如何创建安全的JEXL(脚本)沙箱?
我正在创建要执行的JEXL脚本的沙箱,以使恶意用户无法访问我们为其提供访问权限的变量之外的数据,也无法在服务器上执行DOS攻击。我想为其他这样做的人提供文档,也让其他人对此方法有所投入。
以下是我知道需要解决的问题的列表:
- 仅允许使用白名单上的“ new”实例化类。
- 不允许访问任何类的getClass方法,因为这样便可以调用forName并且可以访问任何类。
- 限制对文件等资源的访问。
- 只允许表达式有一定的执行时间,以便我们可以限制表达式消耗的资源量。
这不适用于JEXL,但可能适用于您使用的脚本语言:
- 不允许对象具有自定义的finalize方法,因为finalize方法是从终结器线程调用的,并且将使用原始AccessControlContext执行,而不是使用用于创建对象并在其中执行代码的方法。
回答:
这是我处理每种情况的方法。我已经创建了单元测试来测试每种情况,并且已经验证它们可以工作。
- JEXL使这个过程非常容易。只需创建一个自定义的ClassLoader。重写两个loadClass()方法。在JexlEngine上,调用setClassLoader()。 
- 同样,JEXL使这变得非常容易。您必须同时阻止“ .class”和“ .getClass()”。创建您自己的扩展UberspectImpl的Uberspect类。覆盖getPropertyGet,如果标识符等于“ class”,则返回null。重写getMethod,如果方法等于“ getClass”,则返回null。构造JexlEngine时,请传递对您的Uberspect实现的引用。 - class MyUberspectImpl extends UberspectImpl {- public MyUberspectImpl(Log jexlLog) { - super(jexlLog); - } - @Override - public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) { - // for security we do not allow access to .class property - if ("class".equals(identifier)) throw new RuntimeException("Access to getClass() method is not allowed"); - JexlPropertyGet propertyGet = super.getPropertyGet(obj, identifier, info); - return propertyGet; - } - @Override - public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) { - // for security we do not allow access to .getClass() method - if ("getClass".equals(method)) throw new RuntimeException("Access to getClass() method is not allowed"); - return super.getMethod(obj, method, args, info); - } - } 
- 您可以使用Java的AccessController机制来执行此操作。我将对此进行快速介绍。使用-Djava.security.policy = policyfile启动Java。创建一个名为policyfile的文件,其中包含以下行:grant {权限java.security.AllPermission; }; 通过以下调用设置默认的SecurityManager:System.setSecurityManager(new SecurityManager()); 现在,您可以控制权限,并且您的应用默认具有所有权限。当然,最好将应用程序的权限限制为仅需要它的权限。接下来,创建一个将访问权限限制到最低限度的AccessControlContext,并调用AccessController.doPrivileged()并传递AccessControlContext,然后在doPrivileged()中执行JEXL脚本。这是一个演示此操作的小程序。JEXL脚本调用System.exit(1),如果不是, - 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(); - } 
- 技巧是在脚本完成之前中断脚本。我发现做到这一点的一种方法是创建一个自定义JexlArithmetic类。然后重写该类中的每个方法,并在调用超类中的real方法之前检查脚本是否应停止执行。我正在使用ExecutorService创建线程。调用Future.get()时,要经过等待时间。如果抛出TimeoutException,则调用Future.cancel(),这会中断运行脚本的线程。在新的JexlArithmetic类的每个重写的方法内部,检查Thread.interrupted(),如果为true,则抛出java.util.concurrent.CancellationException。是否有更好的位置放置将在执行脚本时定期执行的代码,以便可以将其中断? 
这是MyJexlArithmetic类的节选。您必须添加所有其他方法:
    public class MyJexlArithmetic extends JexlArithmetic {        public MyJexlArithmetic(boolean lenient) {
            super(lenient);
        }
        private void checkInterrupted() {
            if (Thread.interrupted()) throw new CancellationException();
        }
        @Override
        public boolean equals(Object left, Object right) {
            checkInterrupted();
            return super.equals(left, right); //To change body of generated methods, choose Tools | Templates.
        }
        @Override
        public Object add(Object left, Object right) {
            checkInterrupted();
            return super.add(left, right);
        }
    }
这是我实例化JexlEngine的方法:
        Log jexlLog = LogFactory.getLog("JEXL");        Map <String, Object> functions = new HashMap();
        jexlEngine = new JexlEngine(new MyUberspectImpl(jexlLog), new MyJexlArithmetic(false), functions, jexlLog);
以上是 如何创建安全的JEXL(脚本)沙箱? 的全部内容, 来源链接: utcz.com/qa/399088.html




