更新: これはすべて JEXL 2.0.1 を使用して行われました。新しいバージョンで動作するようにするには、これを調整する必要がある場合があります。
これが、これらの各ケースに対処するための私のアプローチです。これらの各ケースをテストする単体テストを作成し、それらが機能することを確認しました。
JEXL を使用すると、これが非常に簡単になります。カスタム ClassLoader を作成するだけです。2 つの loadClass() メソッドをオーバーライドします。JexlEngine で setClassLoader() を呼び出します。
繰り返しますが、JEXL を使用すると、これが非常に簡単になります。「.class」と「.getClass()」の両方をブロックする必要があります。UberspectImpl を拡張する独自の Uberspect クラスを作成します。getPropertyGet をオーバーライドし、identifier が「class」に等しい場合は null を返します。メソッドが「getClass」に等しい場合は、getMethod をオーバーライドして 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 という名前のファイルを作成します。}; 次の呼び出しでデフォルトの 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();
}
これの秘訣は、スクリプトが終了する前に中断することです。これを行う方法の 1 つは、カスタム 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);