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

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

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

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

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

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

回答:

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

  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);

以上是 如何创建安全的JEXL(脚本)沙箱? 的全部内容, 来源链接: utcz.com/qa/399088.html

回到顶部