在允许用户提交他们自己的代码以供服务器运行的模拟服务器环境中,任何用户提交的代码都可以在沙箱中运行,这与使用Applet在浏览器中不同,这显然是有利的.我希望能够利用JVM本身,而不是添加另一个VM层来隔离这些提交的组件.
使用现有的Java沙箱模型似乎可以实现这种限制,但是有一种动态方法可以仅为正在运行的应用程序的用户提交的部分启用它吗?
在自己的线程中运行不受信任的代码.例如,这可以防止无限循环等问题,并使未来的步骤更容易.让主线程等待线程完成,如果花费太长时间,请使用Thread.stop将其终止.不推荐使用Thread.stop,但由于不受信任的代码不能访问任何资源,因此将其删除是安全的.
在该线程上设置SecurityManager.创建一个SecurityManager的子类,它覆盖checkPermission(Permission perm),只是为除了少数几个权限之外的所有权限抛出SecurityException.这里有一个方法列表和权限:Java TM 6 SDK 中的权限.
使用自定义ClassLoader加载不受信任的代码.您的类加载器将被调用所有不受信任的代码使用的类,因此您可以执行诸如禁用对单个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的 defineClass函数将其加载到JVM中.要为此加载的类设置安全上下文,您需要指定一个ProtectionDomain.ProtectionDomain的最小构造函数需要CodeSource和PermissionCollection.PermissionCollection是您在这里主要使用的对象 - 您可以使用它来指定加载的类具有的确切权限.这些权限最终应由JVM的AccessController强制执行.
这里有很多可能的错误点,在实现任何内容之前,你应该非常小心地完全理解所有内容.
的Java的沙箱是用于执行Java代码与一组有限的权限的文库.它可用于仅允许访问一组列入白名单的类和资源.它似乎无法限制对单个方法的访问.它使用具有自定义类加载器和安全管理器的系统来实现此目的.
我没有使用它,但它看起来设计得很好,而且记录得很好.
@waqas给出了一个非常有趣的答案,解释了如何实现这一点.但是,将这些安全关键和复杂的代码留给专家会更安全.
请注意,自2013年以来该项目尚未更新,创作者将其描述为"实验性".它的主页已经消失,但Source Forge条目仍然存在.
从项目网站改编的示例代码:
SandboxService sandboxService = SandboxServiceImpl.getInstance(); // Configure context SandboxContext context = new SandboxContext(); context.addClassForApplicationLoader(getClass().getName()); context.addClassPermission(AccessType.PERMIT, "java.lang.System"); // Whithout this line we get a SandboxException when touching System.out context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream"); String someValue = "Input value"; class TestEnvironment implements SandboxedEnvironment{ @Override public String execute() throws Exception { // This is untrusted code System.out.println(someValue); return "Output value"; } }; // Run code in sandbox. Pass arguments to generated constructor in TestEnvironment. SandboxedCallResult result = sandboxService.runSandboxed(TestEnvironment.class, context, this, someValue); System.out.println(result.get());