您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

如何创建安全的JEXL(脚本)沙箱?

如何创建安全的JEXL(脚本)沙箱?

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

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);
其他 2022/1/1 18:26:41 有307人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶