6

JEXL スクリプトを実行するためのサンドボックスを作成しているため、悪意のあるユーザーは、アクセスを許可した変数の外部のデータにアクセスできず、サーバーで DOS 攻撃を実行することもできません。これを行っている他の人のためにこれを文書化し、他の人の意見をアプローチに取り入れたいと思います。

以下は、対処する必要があると私が認識している事項のリストです。

  1. ホワイトリストにある「new」を使用したクラスのインスタンス化のみを許可します。
  2. forName を呼び出すことができ、任意のクラスにアクセスできるため、任意のクラスで getClass メソッドへのアクセスを許可しないでください。
  3. ファイルなどのリソースへのアクセスを制限します。
  4. 式が消費するリソースの量を制限できるように、式の実行に一定の時間だけを許可します。

これは JEXL には当てはまりませんが、使用しているスクリプト言語には当てはまる場合があります。

  1. finalize メソッドはファイナライザー スレッドから呼び出され、オブジェクトの作成とコードの実行に使用されたものではなく、元の AccessControlContext で実行されるため、オブジェクトにカスタムの finalize メソッドを許可しないでください。
4

1 に答える 1

10

更新: これはすべて JEXL 2.0.1 を使用して行われました。新しいバージョンで動作するようにするには、これを調整する必要がある場合があります。

これが、これらの各ケースに対処するための私のアプローチです。これらの各ケースをテストする単体テストを作成し、それらが機能することを確認しました。

  1. JEXL を使用すると、これが非常に簡単になります。カスタム ClassLoader を作成するだけです。2 つの loadClass() メソッドをオーバーライドします。JexlEngine で setClassLoader() を呼び出します。

  2. 繰り返しますが、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);
        }
    
    }
    
  3. これは、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();
    }
    
  4. これの秘訣は、スクリプトが終了する前に中断することです。これを行う方法の 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);
于 2011-07-01T17:10:43.227 に答える