我想让我的应用程序运行其他人的代码,即插件。但是,我有什么选择才能使其安全,因此他们不会编写恶意代码。我如何控制他们能做什么或不能做什么?
我偶然发现JVM有一个“内置沙盒”功能 - 它是什么,这是唯一的方法吗?是否有用于制作沙箱的第三方Java库?
我有哪些选择?赞赏指南和示例的链接!
答案 0 :(得分:23)
您正在寻找security manager。您可以通过指定policy来限制应用程序的权限。
答案 1 :(得分:18)
定义并注册您自己的安全管理器将允许您限制代码的作用 - 请参阅SecurityManager的oracle文档。
另外,请考虑创建一个单独的机制来加载代码 - 即您可以编写或实例化另一个类加载器以从特殊位置加载代码。您可能有一个加载代码的约定 - 例如从特殊目录或特殊格式的zip文件(作为WAR文件和JAR文件)。如果您正在编写一个类加载器,它会使您处于必须完成工作以加载代码的位置。这意味着如果您想要拒绝某些内容(或某些依赖项),则无法加载代码。 http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html
答案 2 :(得分:5)
查看the java-sandbox project,它可以轻松创建非常灵活的沙箱来运行不受信任的代码。
答案 3 :(得分:4)
对于AWT / Swing应用程序,您需要使用非标准AppContext
类,它可能随时更改。因此,为了有效,您需要启动另一个进程来运行插件代码,并处理两者之间的通信(有点像Chrome)。插件流程需要SecurityManager
集和ClassLoader
来隔离插件代码并将适当的ProtectionDomain
应用于插件类。
答案 4 :(得分:3)
以下是使用SecurityManager解决问题的方法:
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.
}
答案 5 :(得分:0)
关于这个问题的讨论激发了我启动自己的沙盒项目。
https://github.com/Black-Mantha/sandbox
其中我遇到了一个重要的安全问题:“如何让沙箱外的代码绕过SecurityManager
?”
我将沙盒代码放在自己的ThreadGroup中,并且在该组外部时始终授予权限。如果您仍需要在该组中运行特权代码(例如,在回调中),则可以使用ThreadLocal仅为该Thread设置标志。类加载器将阻止沙箱访问ThreadLocal。另外,如果你这样做,你需要禁止使用终结器,因为它们在ThreadGroup外部的专用线程中运行。
答案 6 :(得分:0)
花了一天的时间深入研究Java安全API之后,我发现了一个非常简单的解决方案,用于在Permissions限制的沙箱中执行不受信任的代码:
这是(简化的)源代码:
package org.codehaus.commons.compiler;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
public final
class Sandbox {
static {
if (System.getSecurityManager() == null) {
// Before installing the security manager, configure a decent ("positive") policy.
Policy.setPolicy(new Policy() {
@Override public boolean
implies(ProtectionDomain domain, Permission permission) { return true; }
});
System.setSecurityManager(new SecurityManager());
}
}
private final AccessControlContext accessControlContext;
/**
* @param permissions Will be applied on later calls to {@link #confine(PrivilegedAction)} and {@link
* #confine(PrivilegedExceptionAction)}
*/
public
Sandbox(PermissionCollection permissions) {
this.accessControlContext = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, permissions)
});
}
/**
* Runs the given <var>action</var>, confined by the permissions configured through the {@link
* #Sandbox(PermissionCollection) constructor}.
*
* @return The value returned by the <var>action</var>
*/
public <R> R
confine(PrivilegedAction<R> action) {
return AccessController.doPrivileged(action, this.accessControlContext);
}
public <R> R
confine(PrivilegedExceptionAction<R> action) throws Exception {
try {
return AccessController.doPrivileged(action, this.accessControlContext);
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
}