34

質問の概要:以下のコードを変更して、信頼できない動的にロードされたコードがセキュリティサンドボックスで実行され、アプリケーションの残りの部分が制限されないようにするにはどうすればよいですか?なぜURLClassLoaderはそれが言うようにそれを処理しないのですか?

編集:AniBに応答するように更新されました。

編集2:更新されたPluginSecurityManagerを追加しました。

私のアプリケーションにはプラグインメカニズムがあり、サードパーティが特定のインターフェイスを実装するクラスを含むJARを提供できます。URLClassLoaderを使用して、そのクラスをロードしてインスタンス化することができます。問題ありません。コードは信頼できない可能性があるため、誤動作を防ぐ必要があります。たとえば、プラグインコードを別のスレッドで実行して、無限ループに入ったり、時間がかかりすぎたりした場合にプラグインコードを強制終了できるようにします。しかし、ネットワーク接続を確立したり、ハードドライブ上のファイルにアクセスしたりできないように、セキュリティサンドボックスを設定しようとすると、私は前向きになります。私の努力は常にプラグインに影響を与えない(アプリケーションと同じ権限を持っている)か、アプリケーションを制限することになります。メインのアプリケーションコードで、必要なことをほぼすべて実行できるようにしたいのですが、

このテーマに関するドキュメントとオンラインリソースは複雑で、混乱を招き、矛盾しています。カスタムSecurityManagerを提供する必要があることをさまざまな場所(この質問など)で読みましたが、JVMがJARのクラスを遅延ロードするため、試してみると問題が発生します。したがって、問題なくインスタンス化できますが、同じJARから別のクラスをインスタンス化するロードされたオブジェクトのメソッドを呼び出すと、JARから読み取る権利が拒否されたために爆発します。

理論的には、SecurityManagerのFilePermissionをチェックして、独自のJARからロードしようとしているかどうかを確認できます。それは問題ありませんが、URLClassLoaderのドキュメントには、「ロードされるクラスには、デフォルトで、URLClassLoaderの作成時に指定されたURLにアクセスするためのアクセス許可のみが付与されます」と記載されています。では、なぜカスタムSecurityManagerが必要なのですか?URLClassLoaderはこれを処理するだけではいけませんか?なんでそうじゃないの?

問題を再現する簡単な例を次に示します。

メインアプリケーション(信頼できる)

PluginTest.java

package test.app;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

import test.api.Plugin;

public class PluginTest {
    public static void pluginTest(String pathToJar) {
        try {
            File file = new File(pathToJar);
            URL url = file.toURI().toURL();
            URLClassLoader cl = new URLClassLoader(new java.net.URL[] { url });
            Class<?> clazz = cl.loadClass("test.plugin.MyPlugin");
            final Plugin plugin = (Plugin) clazz.newInstance();
            PluginThread thread = new PluginThread(new Runnable() {
                @Override
                public void run() {
                    plugin.go();
                }
            });
            thread.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Plugin.java

package test.api;

public interface Plugin {
    public void go();
}

PluginSecurityManager.java

package test.app;

public class PluginSecurityManager extends SecurityManager {
    private boolean _sandboxed;

    @Override
    public void checkPermission(Permission perm) {
        check(perm);
    } 

    @Override
    public void checkPermission(Permission perm, Object context) {
        check(perm);
    }

    private void check(Permission perm) {
        if (!_sandboxed) {
            return;
        }

        // I *could* check FilePermission here, but why doesn't
        // URLClassLoader handle it like it says it does?

        throw new SecurityException("Permission denied");
    }

    void enableSandbox() {
    _sandboxed = true;
    }

    void disableSandbox() {
        _sandboxed = false;
    }
}

PluginThread.java

package test.app;

class PluginThread extends Thread {
    PluginThread(Runnable target) {
        super(target);
    }

    @Override
    public void run() {
        SecurityManager old = System.getSecurityManager();
        PluginSecurityManager psm = new PluginSecurityManager();
        System.setSecurityManager(psm);
        psm.enableSandbox();
        super.run();
        psm.disableSandbox();
        System.setSecurityManager(old);
    }
}

プラグインJAR(信頼できない)

MyPlugin.java

package test.plugin;

public MyPlugin implements Plugin {
    @Override
    public void go() {
        new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
        doSomethingDangerous(); // permitted without a SecurityManager
    }

    private void doSomethingDangerous() {
        // use your imagination
    }
}

更新: プラグインコードが実行される直前に、プラグインセキュリティマネージャーに通知して、使用しているクラスソースを認識できるように変更しました。次に、そのクラスのソースパスの下にあるファイルへのファイルアクセスのみを許可します。これには、アプリケーションの開始時にセキュリティマネージャを一度設定するだけで、プラグインコードを入力および終了するときにセキュリティマネージャを更新できるという優れた利点もあります。

これで問題はほぼ解決しますが、他の質問には答えられません。URLClassLoaderが、言われているようにこれを処理しないのはなぜですか。誰かがその質問に対する答えを持っているかどうかを確認するために、この質問をしばらく開いたままにしておきます。もしそうなら、その人は受け入れられた答えを得るでしょう。それ以外の場合は、URLClassLoaderのドキュメントがあり、カスタムSecurityManagerを作成するための彼のアドバイスが正しいことを前提として、AniBに授与します。

PluginThreadは、クラスファイルへのパスであるPluginSecurityManagerのclassSourceプロパティを設定する必要があります。PluginSecurityManagerは次のようになります。

package test.app;

public class PluginSecurityManager extends SecurityManager {
    private String _classSource;

    @Override
    public void checkPermission(Permission perm) {
        check(perm);
    } 

    @Override
    public void checkPermission(Permission perm, Object context) {
        check(perm);
    }

    private void check(Permission perm) {
        if (_classSource == null) {
            // Not running plugin code
            return;
        }

        if (perm instanceof FilePermission) {
            // Is the request inside the class source?
            String path = perm.getName();
            boolean inClassSource = path.startsWith(_classSource);

            // Is the request for read-only access?
            boolean readOnly = "read".equals(perm.getActions());

            if (inClassSource && readOnly) {
                return;
            }
        }

        throw new SecurityException("Permission denied: " + perm);
    }

    void setClassSource(String classSource) {
    _classSource = classSource;
    }
}
4

3 に答える 3

8

ドキュメントから:
The AccessControlContext of the thread that created the instance of URLClassLoader will be used when subsequently loading classes and resources.

The classes that are loaded are by default granted permission only to access the URLs specified when the URLClassLoader was created.

URLClassLoader は、まさにそのとおりに動作しています。AccessControlContext を確認する必要があります。基本的に、 AccessControlContext で参照されているスレッドには、あなたが考えていることを行う権限がありません。

于 2010-10-19T17:46:28.810 に答える
7

アプリケーション内で Groovy スクリプトを実行しているときに、次のアプローチを使用します。明らかに、スクリプトが (意図的または意図せずに) System.exit を実行するのを防ぎたい

通常の方法で Java SecurityManager をインストールします。

-Djava.security.manager -Djava.security.policy=<policy file>

では、<policy file>アプリケーションにすべてのアクセス許可を与えます (アプリケーションを完全に信頼します)。

grant {
    permission java.security.AllPermission;
};

Groovy スクリプトが実行される部分の機能を制限します。

list = AccessController.doPrivileged(new PrivilegedExceptionAction<List<Stuff>> () {
    public List<Stuff> run() throws Exception {
        return groovyToExecute.someFunction();
    }
}, allowedPermissionsAcc);

allowedPermissionsAcc変更されないため、静的ブロックで作成します

private static final AccessControlContext allowedPermissionsAcc; 
static {    // initialization of the allowed permissions
    PermissionCollection allowedPermissions = new Permissions();
    allowedPermissions.add(new RuntimePermission("accessDeclaredMembers"));
    // ... <many more permissions here> ...

    allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] {
        new ProtectionDomain(null, allowedPermissions)});
}

ここで注意が必要なのは、適切なアクセス許可を見つけることです。

特定のライブラリへのアクセスを許可したい場合、それらがセキュリティ マネージャを念頭に置いて作成されておらず、セキュリティ マネージャを適切に処理していないことにすぐに気付くでしょう。必要なアクセス許可を見つけるのは非常に難しい場合があります。動作が異なる可能性があるため、Maven Surefire プラグインを介して UnitTests を実行したり、Linux/Windows などの異なるプラットフォームで実行したりする場合は、追加の問題が発生します :-(。ただし、これらの問題は別のトピックです。

于 2011-08-24T07:58:28.883 に答える
5

a を実装するのSecurityManagerがおそらく最善の方法です。オーバーライドする必要がありますcheckPermission。そのメソッドは、Permission渡されたオブジェクトを見て、特定のアクションが危険かどうかを判断します。このようにして、一部のアクセス許可を許可し、他のアクセス許可を禁止することができます。

あなたが使っていた習慣について説明できますSecurityManagerか?

于 2010-10-18T01:48:19.777 に答える