针对 Java 应用程序中的恶意代码的沙盒
在允许用户提交自己的代码由服务器运行的模拟服务器环境中,任何用户提交的代码在沙箱中运行显然是有利的,这与小程序在浏览器中一样。我希望能够利用 JVM 本身,而不是添加另一个 VM 层来隔离这些提交的组件。
使用现有的 Java 沙盒模型似乎可以实现这种限制,但是是否有一种动态方法可以仅针对正在运行的应用程序的用户提交部分启用此限制?
在允许用户提交自己的代码由服务器运行的模拟服务器环境中,任何用户提交的代码在沙箱中运行显然是有利的,这与小程序在浏览器中一样。我希望能够利用 JVM 本身,而不是添加另一个 VM 层来隔离这些提交的组件。
使用现有的 Java 沙盒模型似乎可以实现这种限制,但是是否有一种动态方法可以仅针对正在运行的应用程序的用户提交部分启用此限制?
在其自己的线程中运行不受信任的代码。例如,这可以防止无限循环等问题,并使将来的步骤更容易。让主线程等待线程完成,如果花费的时间太长,请使用 Thread.stop 终止它。Thread.stop 已被弃用,但由于不受信任的代码不应该访问任何资源,因此可以安全地杀死它。
在该线程上设置安全管理器。创建一个 SecurityManager 子类,该子类将覆盖 checkPermission(Permission perm),以简单地为除少数权限之外的所有权限抛出一个 SecurityException。这里有一个方法列表和它们需要的权限:JavaTM 6 SDK中的权限。
使用自定义类加载器加载不受信任的代码。您的类装入器将被不受信任的代码使用的所有类调用,因此您可以执行诸如禁用对单个JDK类的访问之类的操作。要做的是有一个允许的JDK类的白名单。
您可能希望在单独的 JVM 中运行不受信任的代码。虽然前面的步骤会使代码安全,但隔离的代码仍然可以做一件令人讨厌的事情:分配尽可能多的内存,这会导致主应用程序的可见占用空间增长。
JSR 121:应用程序隔离 API 规范旨在解决此问题,但遗憾的是,它还没有实现。
这是一个非常详细的话题,我主要是把这些都写在我的头顶上。
但无论如何,一些不完美的,使用风险自负的,可能是错误的(伪)代码:
类加载器
class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name is white-listed JDK class) return super.loadClass(name);
return findClass(name);
}
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the untrusted class data here
}
}
安全管理器
class MySecurityManager extends SecurityManager {
private Object secret;
public MySecurityManager(Object pass) { secret = pass; }
private void disable(Object pass) {
if (pass == secret) secret = null;
}
// ... override checkXXX method(s) here.
// Always allow them to succeed when secret==null
}
线
class MyIsolatedThread extends Thread {
private Object pass = new Object();
private MyClassLoader loader = new MyClassLoader();
private MySecurityManager sm = new MySecurityManager(pass);
public void run() {
SecurityManager old = System.getSecurityManager();
System.setSecurityManager(sm);
runUntrustedCode();
sm.disable(pass);
System.setSecurityManager(old);
}
private void runUntrustedCode() {
try {
// run the custom class's main method for example:
loader.loadClass("customclassname")
.getMethod("main", String[].class)
.invoke(null, new Object[]{...});
} catch (Throwable t) {}
}
}
显然,这样的计划引起了各种安全问题。Java有一个严格的安全框架,但它并非微不足道。不应忽视搞砸它并让无特权用户访问重要系统组件的可能性。
撇开这个警告不谈,如果你以源代码的形式接受用户输入,你需要做的第一件事就是将其编译为Java字节码。AFIAK,这不能在本地完成,所以你需要对javac进行系统调用,并将源代码编译为磁盘上的字节码。这是一个可以用作此主题的教程。编辑:正如我在评论中学到的,您实际上可以使用javax.tools.JavaCompiler从源代码本地编译Java代码
一旦你有了JVM字节码,你就可以使用ClassLoader的de defineClass函数把它加载到JVM中。要为此装入的类设置安全上下文,您需要指定一个保护域。保护域的最小构造函数需要代码源和权限集合。权限集合是您在此处的主要用途对象 - 您可以使用它来指定加载的类具有的确切权限。这些权限最终应由 JVM 的 AccessController 强制执行。
这里有很多可能的错误点,在实现任何东西之前,你应该非常小心地完全理解所有内容。