更新:这一切都是使用 JEXL 2.0.1 完成的。您可能必须对其进行调整以使其适用于较新的版本。
这是我处理每种情况的方法。我已经创建了单元测试来测试这些案例中的每一个,并且我已经验证了它们是否有效。
JEXL 使这非常容易。只需创建一个自定义 ClassLoader。覆盖两个 loadClass() 方法。在 JexlEngine 上调用 setClassLoader()。
同样,JEXL 使这变得非常容易。您必须同时阻止“.class”和“.getClass()”。创建您自己的扩展 UberspectImpl 的 Uberspect 类。覆盖 getPropertyGet,如果标识符等于“类”返回 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 { permission 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 类。然后覆盖该类中的每个方法,并在调用超类中的真实方法之前检查脚本是否应该停止执行。我正在使用 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);