Java安全性:通过URLClassLoader加载沙盒插件 问题摘要:如何修改下面的代码,使不受信任、动态加载的代码在安全沙箱中运行,而应用程序的其余部分保持不受限制?为什么URLClassLoader不能像它说的那样处理它呢?
编辑:更新以响应Ani B。 编辑2:添加了更新的插件安全管理器。 我的应用程序有一个插件机制,第三方可以提供一个JAR,其中包含一个实现特定接口的类。使用URLClassLoader,我能够加载该类并实例化它,没有问题。因为代码可能不可信,所以我需要防止它出现错误行为。例如,我在一个单独的线程中运行插件代码,以便在它进入无限循环或花费太长时间时可以终止它。但是,试图为他们设置一个安全沙箱,使他们无法进行网络连接或访问硬盘上的文件,这让我非常恼火。我的努力总是导致要么对插件没有影响(它具有与应用程序相同的权限),要么限制应用程序。我希望主应用程序代码能够做它想做的任何事情,但是插件代码要被锁定 关于这个主题的文档和在线资源是复杂、混乱和矛盾的。我在不同的地方(例如)读到我需要提供一个定制的SecurityManager,但是当我尝试它时,我遇到了问题,因为JVM延迟加载JAR中的类。所以我可以很好地实例化它,但是如果我在加载的对象上调用一个方法来实例化来自同一个JAR的另一个类,它就会崩溃,因为它被拒绝了从JAR读取的权利 理论上,我可以在SecurityManager中检查FilePermission,看看它是否试图从自己的JAR中加载。这很好,但是说:“默认情况下,加载的类只被授予访问创建URLClassLoader时指定的URL的权限。”那么,为什么我甚至需要自定义SecurityManager呢?URLClassLoader不应该处理这个问题吗?为什么不呢 下面是一个简单的例子,再现了这个问题: 主应用程序(受信任) pluginest.java PluginSecurityManager.java PluginThread.java 插件JAR(不可信) MyPlugin.javaJava安全性:通过URLClassLoader加载沙盒插件 问题摘要:如何修改下面的代码,使不受信任、动态加载的代码在安全沙箱中运行,而应用程序的其余部分保持不受限制?为什么URLClassLoader不能像它说的那样处理它呢?,java,classloader,securitymanager,urlclassloader,Java,Classloader,Securitymanager,Urlclassloader,编辑:更新以响应Ani B。 编辑2:添加了更新的插件安全管理器。 我的应用程序有一个插件机制,第三方可以提供一个JAR,其中包含一个实现特定接口的类。使用URLClassLoader,我能够加载该类并实例化它,没有问题。因为代码可能不可信,所以我需要防止它出现错误行为。例如,我在一个单独的线程中运行插件代码,以便在它进入无限循环或花费太长时间时可以终止它。但是,试图为他们设置一个安全沙箱,使他们无法进行网络连接或访问硬盘上的文件,这让我非常恼火。我的努力总是导致要么对插件没有影响(它具有与应用
更新: 我更改了它,以便在插件代码即将运行之前,它通知PluginSecurityManager,以便它知道使用的是什么类源。然后,它将只允许对该类源路径下的文件进行文件访问。这还有一个很好的优点,我可以在应用程序开始时设置一次安全管理器,然后在输入和退出插件代码时更新它 这基本上解决了问题,但没有回答我的另一个问题:为什么URLClassLoader不能像它说的那样为我处理这个问题?我会把这个问题留一段时间,看看有没有人能回答这个问题。如果是这样,该人将得到公认的答案。否则,我将把它授予Ani B。前提是URLClassLoader文档是假的,并且他关于创建自定义SecurityManager的建议是正确的 PluginThread必须在PluginSecurityManager上设置classSource属性,该属性是类文件的路径。PlugUnsecurityManager现在看起来像这样:
package test.app;
public class PluginSecurityManager extends SecurityManager {
private String _classSource;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (_classSource == null) {
// Not running plugin code
return;
}
if (perm instanceof FilePermission) {
// Is the request inside the class source?
String path = perm.getName();
boolean inClassSource = path.startsWith(_classSource);
// Is the request for read-only access?
boolean readOnly = "read".equals(perm.getActions());
if (inClassSource && readOnly) {
return;
}
}
throw new SecurityException("Permission denied: " + perm);
}
void setClassSource(String classSource) {
_classSource = classSource;
}
}
实现
SecurityManager
可能是最好的方法。您必须覆盖checkPermission
。该方法将查看传递给它的权限
对象,并确定某个操作是否危险。通过这种方式,您可以允许某些权限,而不允许其他权限
您能描述一下您使用的自定义安全管理器吗?来自文档:
创建URLClassLoader实例的线程的AccessControlContext将在随后加载类和资源时使用。
默认情况下,加载的类仅被授予访问创建URLClassLoader时指定的URL的权限。
URLClassLoader完全按照它所说的做,AccessControlContext是您需要查看的。基本上,AccessControlContext中引用的线程没有权限执行您认为它可以执行的操作。在应用程序中运行一些Groovy脚本时,我使用以下方法。我显然希望防止脚本(有意或无意)运行System.exit
我以通常的方式安装java SecurityManager:
-Djava.security.manager -Djava.security.policy=<policy file>
我限制了Groovy脚本运行部分的功能:
list = AccessController.doPrivileged(new PrivilegedExceptionAction<List<Stuff>> () {
public List<Stuff> run() throws Exception {
return groovyToExecute.someFunction();
}
}, allowedPermissionsAcc);
现在棘手的部分是找到正确的权限
如果您想允许访问某些库,您很快就会意识到,编写这些库时没有考虑到安全管理器,也没有很好地处理安全管理器,要找出它们需要的权限可能非常棘手。如果您想通过Maven Surefire插件运行UnitTests,或者在不同的平台(如Linux/Windows)上运行UnitTests,您将遇到更多的问题,因为行为可能会有所不同:-(.但这些问题是另一个主题这是一个相关的问题,可能会有帮助::)是的,事实上我自己就链接到了这个问题。问题是,这里提出的解决方案是不完整的。它没有完全实现SecurityManager,这是这个问题的关键部分。它也没有解释为什么你一开始就需要一个,因为URLClassLoader声称默认情况下会为你处理这个问题。你知道不受信任的代码可能在run
返回后在另一个线程中运行代码吗?我有一个非常类似的问题,我正在尝试解决这个问题。然而,PluginThread.run方法中是否存在一个大问题,即更改整个系统的安全管理器。如果这
package test.app;
class PluginThread extends Thread {
PluginThread(Runnable target) {
super(target);
}
@Override
public void run() {
SecurityManager old = System.getSecurityManager();
PluginSecurityManager psm = new PluginSecurityManager();
System.setSecurityManager(psm);
psm.enableSandbox();
super.run();
psm.disableSandbox();
System.setSecurityManager(old);
}
}
package test.plugin;
public MyPlugin implements Plugin {
@Override
public void go() {
new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
doSomethingDangerous(); // permitted without a SecurityManager
}
private void doSomethingDangerous() {
// use your imagination
}
}
package test.app;
public class PluginSecurityManager extends SecurityManager {
private String _classSource;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (_classSource == null) {
// Not running plugin code
return;
}
if (perm instanceof FilePermission) {
// Is the request inside the class source?
String path = perm.getName();
boolean inClassSource = path.startsWith(_classSource);
// Is the request for read-only access?
boolean readOnly = "read".equals(perm.getActions());
if (inClassSource && readOnly) {
return;
}
}
throw new SecurityException("Permission denied: " + perm);
}
void setClassSource(String classSource) {
_classSource = classSource;
}
}
-Djava.security.manager -Djava.security.policy=<policy file>
grant {
permission java.security.AllPermission;
};
list = AccessController.doPrivileged(new PrivilegedExceptionAction<List<Stuff>> () {
public List<Stuff> run() throws Exception {
return groovyToExecute.someFunction();
}
}, allowedPermissionsAcc);
private static final AccessControlContext allowedPermissionsAcc;
static { // initialization of the allowed permissions
PermissionCollection allowedPermissions = new Permissions();
allowedPermissions.add(new RuntimePermission("accessDeclaredMembers"));
// ... <many more permissions here> ...
allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, allowedPermissions)});
}