同样,JEXL 让这个变得非常简单。您必须阻止 '.class' 和 '.getClass()'。创建自己的 Uberspect 类,它继承 UberspectImpl。重写 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) {
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) {
if ("getClass".equals(method)) throw new RuntimeException("Access to getClass() method is not allowed");
return super.getMethod(obj, method, args, info);
}
}
你可以使用 Java 的 AccessController 机制来实现这个功能。我会简要介绍一下如何进行操作。在启动 Java 时加上参数 -Djava.security.policy=policyfile。创建一个名为 policyfile 的文件,其中包含以下行:
grant { permission java.security.AllPermission; };
使用以下代码设置默认的 SecurityManager:
System.setSecurityManager(new SecurityManager());
现在,您可以控制权限,并且您的应用程序默认拥有所有权限。当然,最好限制应用程序的权限仅限于所需的权限。接下来,创建一个 AccessControlContext,将权限限制为最低限度,并调用 AccessController.doPrivileged() 并传递 AccessControlContext,然后在 doPrivileged() 内部执行 JEXL 脚本。以下是演示此功能的小程序。JEXL 脚本调用 System.exit(1),如果没有包装在 doPrivileged() 中,则成功终止 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类,然后覆盖该类中的每个方法,在调用超类中的真实方法之前检查脚本是否应该停止执行。我使用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);
}
@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);
forName
情况?同时,Uberspect 也没有帮助。或者我使用它的方式不对。我创建了一个具有自定义 Uberspect 类的 JexlEngine,但它从未验证过.class
的使用。 - yogsma