2011-07-01 59 views
4

我正在为JEXL脚本创建一个沙箱,以便恶意用户无法访问我们允许其访问的变量之外的数据,也无法在服务器上执行DOS攻击。我想为其他人也记录这一点,并让其他人对此方法提出意见。如何创建安全的JEXL(脚本)沙箱?

以下是需要解决的,我知道了事情的清单:

  1. 只允许使用“新”是在白名单上实例化类。
  2. 不允许访问任何类的getClass方法,因为可以调用forName并且可以访问任何类。
  3. 限制对资源(如文件)的访问。
  4. 只允许执行一段时间的表达式,以便我们可以限制它消耗的资源量。

这并不适用于JEXL,但可以适用于脚本语言,使用的是:

  1. 不要让一个对象有一个自定义的finalize方法,因为finalize方法从终结线程调用并将使用最初的AccessControlContext而不是用于创建对象并执行其中的代码的执行。

回答

6

这是我处理这些案件的方法。我创建了单元测试来测试每种情况,并且我已经验证了它们的工作原理。

  1. JEXL使这非常容易。只需创建一个自定义ClassLoader。覆盖两个loadClass()方法。在JexlEngine上调用setClassLoader()。

  2. 再一次,JEXL使这非常容易。您必须同时阻止'.class'和'.getClass()'。创建自己的Uberspect类,它扩展了UberspectImpl。覆盖getPropertyGet,如果标识符等于“class”,则返回null。重写getMethod,如果方法等于“getClass”,则返回null。在构建JexlEngine时,请参考您的Uberspect实现。

  3. 您使用Java的AccessController机制来做到这一点。我会尽快解决这个问题。用-Djava.security.policy = policyfile启动java。创建一个名为policyfile的文件,其中包含以下行: grant {permission java.security.AllPermission; }; 使用此调用设置默认SecurityManager:System.setSecurityManager(new SecurityManager());现在您可以控制权限,默认情况下您的应用具有所有权限。如果您将应用程序的权限限制在当前的要求,那将会更好。接下来,创建一个AccessControlContext,将权限限制为最小值,然后调用AccessController.doPrivileged()并传递AccessControlContext,然后在doPrivileged()内执行JEXL脚本。这是一个小程序,演示了这一点。 JEXL脚本调用System.exit(1),如果未包装在doPrivileged()中,它将成功终止JVM。

    System.out.println("java.security.policy=" + System.getProperty("java.security.policy")); 
    System.setSecurityManager(new SecurityManager()); 
    try { 
        Permissions perms = new Permissions(); 
        perms.add(new RuntimePermission("accessDeclaredMembers")); 
        ProtectionDomain domain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), perms); 
        AccessControlContext restrictedAccessControlContext = new AccessControlContext(new ProtectionDomain[] { domain }); 
    
        JexlEngine jexlEngine = new JexlEngine(); 
        final Script finalExpression = jexlEngine.createScript(
          "i = 0; intClazz = i.class; " 
          + "clazz = intClazz.forName(\"java.lang.System\"); " 
          + "m = clazz.methods; m[0].invoke(null, 1); c"); 
    
        AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 
         @Override 
         public Object run() throws Exception { 
          return finalExpression.execute(new MapContext()); 
         } 
        }, restrictedAccessControlContext); 
    } 
    catch (Throwable ex) { 
        ex.printStackTrace(); 
    } 
    
  4. 这样做的诀窍是在脚本完成之前中断脚本。我发现这样做的一种方法是创建一个自定义的JexlArithmetic类。然后重写该类中的每个方法,然后在超级类中调用真实方法之前,检查脚本是否应该停止执行。我正在使用ExecutorService来创建线程。未来。get()被称为传递等待的时间量。如果引发TimeoutException,则调用Future.cancel(),它会中断运行脚本的Thread。在新JexlArithmetic类的每个重写方法内检查Thread.interrupted(),如果为true,则抛出java.util.concurrent.CancellationException。 有没有更好的位置来放置代码,这些代码会在脚本执行时被定期执行,以便它可以被中断?