【问题标题】:How do I create a Java sandbox?如何创建 Java 沙箱?
【发布时间】:2010-12-15 11:04:51
【问题描述】:

我想让我的应用程序运行其他人的代码,也就是插件。但是,我必须采取哪些措施来确保其安全,以免他们编写恶意代码。我如何控制他们能做什么或不能做什么?

我偶然发现 JVM 具有“内置沙盒”功能 - 它是什么,这是唯一的方法吗?是否有用于制作沙盒的第三方 Java 库?

我有什么选择?感谢您提供指南和示例的链接!

【问题讨论】:

    标签: java security plugins sandbox


    【解决方案1】:

    您正在寻找security manager。您可以通过指定policy 来限制应用程序的权限。

    【讨论】:

      【解决方案2】:
      • 定义和注册您自己的安全管理器将允许您限制代码的作用 - 请参阅 SecurityManager 的 oracle 文档。

      • 另外,考虑创建一个单独的机制来加载代码 - 即您可以编写或实例化另一个 Classloader 以从特殊位置加载代码。您可能有加载代码的约定 - 例如从特殊目录或特殊格式的 zip 文件(如 WAR 文件和 JAR 文件)。如果你正在编写一个类加载器,它会让你不得不做一些工作来加载代码。这意味着如果您看到想要拒绝的某些内容(或某些依赖项),您可能无法加载代码。 http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

      【讨论】:

        【解决方案3】:

        对于 AWT/Swing 应用程序,您需要使用非标准的 AppContext 类,该类可能随时更改。因此,为了有效,您需要启动另一个进程来运行插件代码,并处理两者之间的通信(有点像 Chrome)。插件进程将需要一个SecurityManager 集和一个ClassLoader 来隔离插件代码并将适当的ProtectionDomain 应用于插件类。

        【讨论】:

          【解决方案4】:

          看看the java-sandbox project,它可以轻松创建非常灵活的沙箱来运行不受信任的代码。

          【讨论】:

          • 感谢您发布该库,它使我正在做的事情变得更加容易。
          • 链接已失效。谷歌找到this,是一样的吗?
          • 该项目在sourceforge上可用sourceforge.net/projects/dw-sandbox
          【解决方案5】:

          以下是使用 SecurityManager 解决问题的方法:

          https://svn.code.sf.net/p/loggifier/code/trunk/de.unkrig.commons.lang/src/de/unkrig/commons/lang/security/Sandbox.java

          package de.unkrig.commons.lang.security;
          
          import java.security.AccessControlContext;
          import java.security.Permission;
          import java.security.Permissions;
          import java.security.ProtectionDomain;
          import java.util.Collections;
          import java.util.HashMap;
          import java.util.Map;
          import java.util.WeakHashMap;
          
          import de.unkrig.commons.nullanalysis.Nullable;
          
          /**
           * This class establishes a security manager that confines the permissions for code executed through specific classes,
           * which may be specified by class, class name and/or class loader.
           * <p>
           * To 'execute through a class' means that the execution stack includes the class. E.g., if a method of class {@code A}
           * invokes a method of class {@code B}, which then invokes a method of class {@code C}, and all three classes were
           * previously {@link #confine(Class, Permissions) confined}, then for all actions that are executed by class {@code C}
           * the <i>intersection</i> of the three {@link Permissions} apply.
           * <p>
           * Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any
           * attempts (e.g. of the confined class itself) to release the confinement.
           * <p>
           * Code example:
           * <pre>
           *  Runnable unprivileged = new Runnable() {
           *      public void run() {
           *          System.getProperty("user.dir");
           *      }
           *  };
           *
           *  // Run without confinement.
           *  unprivileged.run(); // Works fine.
           *
           *  // Set the most strict permissions.
           *  Sandbox.confine(unprivileged.getClass(), new Permissions());
           *  unprivileged.run(); // Throws a SecurityException.
           *
           *  // Attempt to change the permissions.
           *  {
           *      Permissions permissions = new Permissions();
           *      permissions.add(new AllPermission());
           *      Sandbox.confine(unprivileged.getClass(), permissions); // Throws a SecurityException.
           *  }
           *  unprivileged.run();
           * </pre>
           */
          public final
          class Sandbox {
          
              private Sandbox() {}
          
              private static final Map<Class<?>, AccessControlContext>
              CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>());
          
              private static final Map<String, AccessControlContext>
              CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap<String, AccessControlContext>());
          
              private static final Map<ClassLoader, AccessControlContext>
              CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>());
          
              static {
          
                  // Install our custom security manager.
                  if (System.getSecurityManager() != null) {
                      throw new ExceptionInInitializerError("There's already a security manager set");
                  }
                  System.setSecurityManager(new SecurityManager() {
          
                      @Override public void
                      checkPermission(@Nullable Permission perm) {
                          assert perm != null;
          
                          for (Class<?> clasS : this.getClassContext()) {
          
                              // Check if an ACC was set for the class.
                              {
                                  AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS);
                                  if (acc != null) acc.checkPermission(perm);
                              }
          
                              // Check if an ACC was set for the class name.
                              {
                                  AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName());
                                  if (acc != null) acc.checkPermission(perm);
                              }
          
                              // Check if an ACC was set for the class loader.
                              {
                                  AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get(clasS.getClassLoader());
                                  if (acc != null) acc.checkPermission(perm);
                              }
                          }
                      }
                  });
              }
          
              // --------------------------
          
              /**
               * All future actions that are executed through the given {@code clasS} will be checked against the given {@code
               * accessControlContext}.
               *
               * @throws SecurityException Permissions are already confined for the {@code clasS}
               */
              public static void
              confine(Class<?> clasS, AccessControlContext accessControlContext) {
          
                  if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) {
                      throw new SecurityException("Attempt to change the access control context for '" + clasS + "'");
                  }
          
                  Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext);
              }
          
              /**
               * All future actions that are executed through the given {@code clasS} will be checked against the given {@code
               * protectionDomain}.
               *
               * @throws SecurityException Permissions are already confined for the {@code clasS}
               */
              public static void
              confine(Class<?> clasS, ProtectionDomain protectionDomain) {
                  Sandbox.confine(
                      clasS,
                      new AccessControlContext(new ProtectionDomain[] { protectionDomain })
                  );
              }
          
              /**
               * All future actions that are executed through the given {@code clasS} will be checked against the given {@code
               * permissions}.
               *
               * @throws SecurityException Permissions are already confined for the {@code clasS}
               */
              public static void
              confine(Class<?> clasS, Permissions permissions) {
                  Sandbox.confine(clasS, new ProtectionDomain(null, permissions));
              }
          
              // Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here.
          
          }
          

          【讨论】:

          【解决方案6】:

          关于这个问题的讨论启发了我开始我自己的沙盒项目。

          https://github.com/Black-Mantha/sandbox

          在其中我遇到了一个重要的安全问题:“如何让沙箱外的代码绕过SecurityManager?”

          我将沙盒代码放在它自己的 ThreadGroup 中,并且总是在该组之外授予权限。如果您无论如何都需要在该组中运行特权代码(例如在回调中),则可以使用 ThreadLocal 仅为该线程设置标志。类加载器将阻止沙箱访问 ThreadLocal。此外,如果您这样做,您需要禁止使用终结器,因为它们在 ThreadGroup 之外的专用线程中运行。

          【讨论】:

            【解决方案7】:

            在深入研究 Java 安全 API 一天后,我发现了一个非常简单的解决方案,可以在受权限限制的沙箱中执行不受信任的代码:

            https://github.com/janino-compiler/janino/blob/master/commons-compiler/src/main/java/org/codehaus/commons/compiler/Sandbox.java

            这是(简化的)源代码:

            package org.codehaus.commons.compiler;
            
            import java.security.AccessControlContext;
            import java.security.AccessController;
            import java.security.Permission;
            import java.security.PermissionCollection;
            import java.security.Policy;
            import java.security.PrivilegedAction;
            import java.security.PrivilegedActionException;
            import java.security.PrivilegedExceptionAction;
            import java.security.ProtectionDomain;
            
            public final
            class Sandbox {
            
                static {
            
                    if (System.getSecurityManager() == null) {
            
                        // Before installing the security manager, configure a decent ("positive") policy.
                       Policy.setPolicy(new Policy() {
            
                            @Override public boolean
                            implies(ProtectionDomain domain, Permission permission) { return true; }
                        });
            
                        System.setSecurityManager(new SecurityManager());
                    }
                }
            
                private final AccessControlContext accessControlContext;
            
                /**
                 * @param permissions Will be applied on later calls to {@link #confine(PrivilegedAction)} and {@link
                 *                    #confine(PrivilegedExceptionAction)}
                 */
                public
                Sandbox(PermissionCollection permissions) {
                    this.accessControlContext = new AccessControlContext(new ProtectionDomain[] {
                        new ProtectionDomain(null, permissions)
                    });
                }
            
                /**
                 * Runs the given <var>action</var>, confined by the permissions configured through the {@link
                 * #Sandbox(PermissionCollection) constructor}.
                 *
                 * @return The value returned by the <var>action</var>
                 */
                public <R> R
                confine(PrivilegedAction<R> action) {
                    return AccessController.doPrivileged(action, this.accessControlContext);
                }
            
                public <R> R
                confine(PrivilegedExceptionAction<R> action) throws Exception {
                    try {
                        return AccessController.doPrivileged(action, this.accessControlContext);
                    } catch (PrivilegedActionException pae) {
                        throw pae.getException();
                    }
                }
            }
            

            【讨论】:

            • 很好,但要小心 JEP411 (openjdk.java.net/jeps/411),它可悲地弃用安全管理器。像 Apache River(以前很棒的 Jini)这样的项目在围绕这个 JEP 工作时会遇到严重的问题。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-16
            • 2015-06-20
            • 1970-01-01
            • 1970-01-01
            • 2016-03-27
            • 1970-01-01
            相关资源
            最近更新 更多