我想讓我的應用程序運行其他人的代碼,即插件。但是,我必須具備哪些選項才能確保安全,因此不會編寫惡意代碼。我如何控制他們可以或不可以做的事情?如何創建Java沙箱?
我偶然發現JVM有一個「內置沙箱」功能 - 它是什麼,這是唯一的方法?是否有用於製作沙盒的第三方Java庫?
我有什麼選擇?讚賞指南和例子的鏈接!
我想讓我的應用程序運行其他人的代碼,即插件。但是,我必須具備哪些選項才能確保安全,因此不會編寫惡意代碼。我如何控制他們可以或不可以做的事情?如何創建Java沙箱?
我偶然發現JVM有一個「內置沙箱」功能 - 它是什麼,這是唯一的方法?是否有用於製作沙盒的第三方Java庫?
我有什麼選擇?讚賞指南和例子的鏈接!
您正在查找security manager。您可以通過指定policy來限制應用程序的權限。
定義並註冊自己的安全管理將允許你限制的代碼做什麼 - 看Oracle文檔SecurityManager。
此外,請考慮創建單獨的加載代碼的機制 - 即您可以編寫或實例化另一個Classloader以從特殊位置加載代碼。您可能有一個加載代碼的約定 - 例如從特殊目錄或特殊格式的zip文件(如WAR文件和JAR文件)。如果你正在編寫一個類加載器,它會讓你處於加載代碼的工作狀態。這意味着如果你看到一些你想要拒絕的東西(或者某些依賴),你可能無法加載代碼。 http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html
對於需要使用非標準AppContext
類,它可以在任何時候改變一個AWT/Swing應用程序。因此,爲了提高效率,您需要啓動另一個進程來運行插件代碼,並處理兩者之間的通信(有點像Chrome)。插件過程將需要SecurityManager
集合和ClassLoader
來隔離插件代碼並將適當的ProtectionDomain
應用於插件類。
看看the java-sandbox project,它允許輕鬆地創建非常靈活的沙箱來運行不受信任的代碼。
這裏是如何的問題可以用一個安全管理器來解決:
package de.unkrig.commons.lang.security;
import java.security.AccessControlContext;
import java.security.Permission;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import de.unkrig.commons.nullanalysis.Nullable;
/**
* This class establishes a security manager that confines the permissions for code executed through specific classes,
* which may be specified by class, class name and/or class loader.
* <p>
* To 'execute through a class' means that the execution stack includes the class. E.g., if a method of class {@code A}
* invokes a method of class {@code B}, which then invokes a method of class {@code C}, and all three classes were
* previously {@link #confine(Class, Permissions) confined}, then for all actions that are executed by class {@code C}
* the <i>intersection</i> of the three {@link Permissions} apply.
* <p>
* Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any
* attempts (e.g. of the confined class itself) to release the confinement.
* <p>
* Code example:
* <pre>
* Runnable unprivileged = new Runnable() {
* public void run() {
* System.getProperty("user.dir");
* }
* };
*
* // Run without confinement.
* unprivileged.run(); // Works fine.
*
* // Set the most strict permissions.
* Sandbox.confine(unprivileged.getClass(), new Permissions());
* unprivileged.run(); // Throws a SecurityException.
*
* // Attempt to change the permissions.
* {
* Permissions permissions = new Permissions();
* permissions.add(new AllPermission());
* Sandbox.confine(unprivileged.getClass(), permissions); // Throws a SecurityException.
* }
* unprivileged.run();
* </pre>
*/
public final
class Sandbox {
private Sandbox() {}
private static final Map<Class<?>, AccessControlContext>
CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>());
private static final Map<String, AccessControlContext>
CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap<String, AccessControlContext>());
private static final Map<ClassLoader, AccessControlContext>
CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>());
static {
// Install our custom security manager.
if (System.getSecurityManager() != null) {
throw new ExceptionInInitializerError("There's already a security manager set");
}
System.setSecurityManager(new SecurityManager() {
@Override public void
checkPermission(@Nullable Permission perm) {
assert perm != null;
for (Class<?> clasS : this.getClassContext()) {
// Check if an ACC was set for the class.
{
AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS);
if (acc != null) acc.checkPermission(perm);
}
// Check if an ACC was set for the class name.
{
AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName());
if (acc != null) acc.checkPermission(perm);
}
// Check if an ACC was set for the class loader.
{
AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get(clasS.getClassLoader());
if (acc != null) acc.checkPermission(perm);
}
}
}
});
}
// --------------------------
/**
* All future actions that are executed through the given {@code clasS} will be checked against the given {@code
* accessControlContext}.
*
* @throws SecurityException Permissions are already confined for the {@code clasS}
*/
public static void
confine(Class<?> clasS, AccessControlContext accessControlContext) {
if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) {
throw new SecurityException("Attempt to change the access control context for '" + clasS + "'");
}
Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext);
}
/**
* All future actions that are executed through the given {@code clasS} will be checked against the given {@code
* protectionDomain}.
*
* @throws SecurityException Permissions are already confined for the {@code clasS}
*/
public static void
confine(Class<?> clasS, ProtectionDomain protectionDomain) {
Sandbox.confine(
clasS,
new AccessControlContext(new ProtectionDomain[] { protectionDomain })
);
}
/**
* All future actions that are executed through the given {@code clasS} will be checked against the given {@code
* permissions}.
*
* @throws SecurityException Permissions are already confined for the {@code clasS}
*/
public static void
confine(Class<?> clasS, Permissions permissions) {
Sandbox.confine(clasS, new ProtectionDomain(null, permissions));
}
// Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here.
}
關於這個問題的討論,啓發了我開始了我自己的沙箱項目。
https://github.com/Black-Mantha/sandbox
在這裏面我遇到一個重要的安全問題:「你怎麼讓沙箱外部的代碼繞過SecurityManager
?」
我把沙箱代碼放在它自己的ThreadGroup中,並且總是在該組外部授予權限。如果您需要在該組中運行特權代碼(例如在回調中),則可以使用ThreadLocal爲該線程設置一個標誌。類加載器將阻止沙箱訪問ThreadLocal。另外,如果你這樣做,你需要禁止使用終結器,因爲它們在ThreadGroup之外的專用線程中運行。
感謝您發佈該圖書館,它使我的工作更容易。 –
鏈接已死亡。 Google找到[this](https://sourceforge.net/projects/javasandboxlibrary/),是不是一樣? – planetguy32
@Arno Mittelbach現在無法使用 – Sheldon