我正在为JEXL脚本创建一个沙箱来执行,以便恶意用户无法访问我们允许他们访问的变量之外的数据,也无法在服务器上执行DOS攻击.我想把这个记录给任何其他人也这样做,并且也让其他人对这种方法有所了解.
以下是我所知道的需要解决的事项列表:
这不适用于JEXL,但可能适用于您使用的脚本语言:
更新:这都是使用JEXL 2.0.1完成的.您可能需要对其进行调整以使其适用于较新版本.
以下是处理这些案例的方法.我已经创建了单元测试来测试这些案例,并且我已经验证了它们的工作原理.
JEXL让这很容易.只需创建一个自定义ClassLoader.覆盖两个loadClass()方法.在JexlEngine上调用setClassLoader().
同样,JEXL使这很容易.你必须阻止'.class'和'.getClass()'.创建自己的Uberspect类,扩展UberspectImpl.覆盖getPropertyGet,如果identifier等于"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);
}
}
Run Code Online (Sandbox Code Playgroud)您可以使用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),如果它没有包装在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();
}
Run Code Online (Sandbox Code Playgroud)这样做的诀窍是在脚本完成之前中断脚本.我发现这样做的一种方法是创建一个自定义的JexlArithmetic类.然后覆盖该类中的每个方法,并在调用超类中的实际方法之前检查脚本是否应该停止执行.我正在使用ExecutorService来创建线程.当调用Future.get()时,传递等待的时间量.如果抛出TimeoutException,则调用Future.cancel(),它会中断运行脚本的Thread.在新的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);
}
}
Run Code Online (Sandbox Code Playgroud)
以下是我实例化JexlEngine的方法:
Log jexlLog = LogFactory.getLog("JEXL");
Map <String, Object> functions = new HashMap();
jexlEngine = new JexlEngine(new MyUberspectImpl(jexlLog), new MyJexlArithmetic(false), functions, jexlLog);
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2214 次 |
最近记录: |