代码之家  ›  专栏  ›  技术社区  ›  Robert J. Walker

Java安全:通过URLClassLoader加载的沙盒插件

  •  31
  • Robert J. Walker  · 技术社区  · 14 年前

    问题摘要:如何修改下面的代码,使不受信任的动态加载的代码在安全沙箱中运行,而应用程序的其余部分保持不受限制?为什么URLClassLoader不能像它说的那样处理它呢?

    编辑2:添加了更新的PluginSecurityManager。

    我的应用程序有一个插件机制,其中第三方可以提供一个包含实现特定接口的类的JAR。使用URLClassLoader,我可以加载该类并实例化它,没有问题。因为代码可能是不可信的,所以我需要防止它行为不当。例如,我在一个单独的线程中运行插件代码,以便在它进入无限循环或花费太长时间时杀死它。但是,试图为他们设置一个安全沙箱,这样他们就不能进行网络连接或访问硬盘上的文件,这让我非常恼火。我的努力总是导致要么对插件没有影响(它与应用程序具有相同的权限),要么限制应用程序。我希望主应用程序代码能够做任何它想做的事情,但是插件代码要被锁定。

    this question )我需要提供一个自定义的SecurityManager,但是当我尝试它时,我遇到了问题,因为JVM在JAR中懒加载类。所以我可以很好地实例化它,但是如果我对加载的对象调用一个方法,这个方法从同一个JAR中实例化另一个类,它就会爆炸,因为它被拒绝了从JAR中读取的权利。

    理论上,我可以检查SecurityManager中的FilePermission,看看它是否试图从自己的JAR中加载。没关系,但是 the URLClassLoader documentation 他说:“默认情况下,加载的类只被授予访问创建URLClassLoader时指定的url的权限。”那么,为什么我甚至需要一个自定义的SecurityManager?URLClassLoader不应该处理这个问题吗?为什么不呢?

    主应用程序(受信任)

    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();
    }
    

    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(不可信)

    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不能像它所说的那样为我处理这个问题?我将把这个问题留一段时间,看有没有人能回答那个问题。如果是的话,那个人会得到被接受的答案。否则,我将把它授予Ani B,前提是URLClassLoader文档存在,并且他提出的定制SecurityManager的建议是正确的。

    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;
        }
    }
    
    3 回复  |  直到 7 年前
        1
  •  7
  •   Woot4Moo    14 年前

    从文档中:
    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.

        2
  •  7
  •   Maze    13 年前

    我在应用程序中运行一些Groovy脚本时使用以下方法。显然,我想防止脚本(有意或无意地)运行System.exit

    -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)});
    }
    

    现在棘手的部分是找到正确的权限。

        3
  •  5
  •   AniDev    14 年前

    实现 SecurityManager 可能是最好的方法。你必须重写 checkPermission . 这种方法可以看到 Permission

    你能描述一下这个习俗吗 安全管理器 你用过?