如何创建安全的JEXL(脚本)沙箱? [英] How do you create a secure JEXL (scripting) sandbox?

查看:98
本文介绍了如何创建安全的JEXL(脚本)沙箱?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建用于执行JEXL脚本的沙箱,以使恶意用户无法访问我们允许他们访问的变量之外的数据,并且也无法在服务器上进行DOS攻击.我想将文档记录给其他也要这样做的人,也请其他人对此方法提供意见.

I'm creating a sandbox for JEXL scripts to execute in so that a malicious user can't access data outside the variables we give them access to and also can't perform a DOS attack on the server. I'd like to document this for anybody else also doing this and also get other people's input into the approach.

以下是我所知道的需要解决的问题的列表:

The following is a list of the things I'm aware of that needs to be addressed:

  1. 仅允许使用白名单上的"new"实例化类.
  2. 不允许访问任何类的getClass方法,因为这样可以调用forName并且可以访问任何类.
  3. 限制对文件等资源的访问.
  4. 只允许表达式有一定的执行时间,以便我们可以限制表达式消耗的资源量.

这不适用于JEXL,但可能适用于您使用的脚本语言:

This does not apply to JEXL but may apply to the scripting language you are using:

  1. 不允许对象具有自定义的finalize方法,因为finalize方法是从终结器线程调用的,并且将使用原始AccessControlContext执行,而不是使用用于创建对象并在其中执行代码的方法.

推荐答案

更新:这些都是使用JEXL 2.0.1完成的.您可能需要对其进行调整,以使其在较新的版本中使用.

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

Here is my approach for dealing with each of these cases. I've created unit tests to test each of these cases and I have verified that they work.

  1. JEXL使此操作非常容易.只需创建一个自定义的ClassLoader.重写两个loadClass()方法.在JexlEngine上,调用setClassLoader().

  1. JEXL makes this pretty easy. Just create a custom ClassLoader. Override the two loadClass() methods. On JexlEngine call setClassLoader().

同样,JEXL使得此操作非常容易.您必须同时阻止".class"和".getClass()".创建您自己的扩展UberspectImpl的Uberspect类.覆盖getPropertyGet,如果标识符等于"class",则返回null.重写getMethod,如果方法等于"getClass",则返回null.构造JexlEngine时,请传递对您的Uberspect实现的引用.

Again, JEXL makes this pretty easy. You must block both '.class' and '.getClass()'. Create your own Uberspect class which extends UberspectImpl. Override getPropertyGet, if identifier equals "class" return null. Override getMethod, if method equals "getClass" return null. When constructing JexlEngine pass a reference to your Uberspect implementation.

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的文件,其中包含以下行: 授予{权限java.security.AllPermission; }; 通过以下调用设置默认的SecurityManager:System.setSecurityManager(new SecurityManager());现在,您可以控制权限,并且您的应用默认具有所有权限.当然,最好将应用程序的权限限制为仅需要它的权限.接下来,创建一个将访问权限限制到最低限度的AccessControlContext,并调用AccessController.doPrivileged()并传递AccessControlContext,然后在doPrivileged()中执行JEXL脚本.这是一个演示此操作的小程序. JEXL脚本调用System.exit(1),如果未将其包装在doPrivileged()中,它将成功终止JVM.

  • You do this using Java's AccessController mechanism. I'll give a quick run-down of doing this. Start java with -Djava.security.policy=policyfile. Make a file named policyfile containing this line: grant { permission java.security.AllPermission; }; Set the default SecurityManager with this call: System.setSecurityManager(new SecurityManager()); Now you can control permissions and your app by default has all permissions. It would be better if you limit the permissions of your app to only what it requires of course. Next, create an AccessControlContext that limits the permissions to the bare minimum and call AccessController.doPrivileged() and pass the AccessControlContext, then execute the JEXL script inside doPrivileged(). Here is a small program that demonstrates this. The JEXL script calls System.exit(1) and if it isn't wrapped in doPrivileged() it would successfully terminate the 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();
    }
    

  • 与此有关的技巧是在脚本完成之前中断脚本.我发现做到这一点的一种方法是创建一个自定义JexlArithmetic类.然后重写该类中的每个方法,并在调用超类中的real方法之前检查脚本是否应停止执行.我正在使用ExecutorService创建线程.调用Future.get()时,要经过等待时间.如果抛出TimeoutException,则调用Future.cancel(),这会中断运行脚本的线程.在新的JexlArithmetic类的每个重写的方法内部,检查Thread.interrupted(),如果为true,则抛出java.util.concurrent.CancellationException.

  • The trick with this is interrupting the script before it finishes. One way I found to do this is to create a custom JexlArithmetic class. Then override each method in that class and before calling the real method in the super class check if the script should stop executing. I'm using an ExecutorService to create threads. When Future.get() is called pass the amount of time to wait. If a TimeoutException is thrown call Future.cancel() which interrupts the Thread running the script. Inside each overridden method in the new JexlArithmetic class check Thread.interrupted() and if true throw java.util.concurrent.CancellationException. Is there a better location to put code which will get executed regularly as a script is being executed so that it can be interrupted?

    这是MyJexlArithmetic类的节选.您必须添加所有其他方法:

    Here is an excerpt of the MyJexlArithmetic class. You have to add all the other methods:

        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的方式:

    Here is how I am instantiating JexlEngine:

            Log jexlLog = LogFactory.getLog("JEXL");
            Map <String, Object> functions = new HashMap();
            jexlEngine = new JexlEngine(new MyUberspectImpl(jexlLog), new MyJexlArithmetic(false), functions, jexlLog);
    

    这篇关于如何创建安全的JEXL(脚本)沙箱?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆