当前位置: 首页 > 面试题库 >

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

秦雅逸
2023-03-14
问题内容

我正在创建要执行的JEXL脚本的沙箱,以使恶意用户无法访问我们为其提供访问权限的变量之外的数据,也无法在服务器上执行DOS攻击。我想为其他这样做的人提供文档,也让其他人对此方法有所投入。

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

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

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

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

问题答案:

更新:这一切都使用JEXL 2.0.1完成。 您可能需要对此进行调整以使其与较新的版本一起使用。

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

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

  2. 同样,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);
    }
    

    }

  3. 您可以使用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();
    }

  4. 技巧是在脚本完成之前中断脚本。我发现做到这一点的一种方法是创建一个自定义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);


 类似资料:
  • 问题内容: 我需要创建环境来运行可能不受信任的代码。程序允许连接到预配置的地址:端口,而无其他要求(甚至读取系统时间)。我已经编译了班白名单。我搜索了类似的问题,但仅找到基于不推荐使用AFAIK的SecurityManager的模板。有人可以给我一个简单的示例,如何基于安全策略和AccessController在沙箱中运行代码吗? 问题答案: 据我所知,仍然是运行安全检查的SecurityMana

  • 问题内容: 在node.js中安全地运行(可能是恶意的)用户提交的脚本有哪些选项?是否在防止代码访问敏感数据和API的环境中? 是一个诱人的起点…但是似乎那里存在已知问题。 在沙箱模块看起来很有趣,但使用也使我的有点持怀疑态度。 问题答案: 您应该始终在单独的过程中运行不受信任的代码,这正是沙盒模块所做的。一个简单的原因是它将冻结节点。 它从产生一个单独的进程开始,随后将在其stdout上将序列化

  • 问题内容: 这听起来像是一个非常笼统的问题,但这是正确的。 我有一个要求,以创建我的应用程序配置脚本,将生成的这种配置的结果(基本,,)。我的问题是,我应该从哪里开始构建?有没有我可以效仿的例子? 问题答案: 要创建标准的“配置”脚本,您需要GNU autoconf。您可能还需要GNU automake和libtool。 有大量的文档和指导。谷歌搜索“ autoconf automake howt

  • 我在 Jenkins 中创建了一个管道作业,我需要创建一个管道脚本来显示开发阶段的失败和不稳定的构建。在 Jenkins 2.0 中创建此脚本的步骤是什么,或者是否有任何示例管道脚本

  • 问题内容: 我需要通过运行设置服务器的脚本来添加cron作业。我目前正在使用Ubuntu。我可以使用,但这将打开一个编辑器来编辑当前的crontab。我想以编程方式执行此操作。 有可能这样做吗? 问题答案: Cron作业通常存储在每个用户的文件下 您要做的最简单的事情可能就是创建一个配置了作业的文本文件,然后将其复制到cron spool文件夹中,并确保它具有正确的权限(600)。