代码之家  ›  专栏  ›  技术社区  ›  Alan Krueger

Java应用程序中的恶意代码沙箱

  •  86
  • Alan Krueger  · 技术社区  · 15 年前

    在允许用户提交自己的代码供服务器运行的仿真服务器环境中,显然有利于任何用户提交的代码在沙盒中运行,这与浏览器中的小程序不同。我希望能够利用JVM本身,而不是添加另一个VM层来隔离这些提交的组件。

    使用现有的Java沙箱模型,这种限制似乎是可能的,但是是否有一种动态的方式来为运行的应用程序的用户提交的部分启用该限制?

    7 回复  |  直到 8 年前
        1
  •  106
  •   waqas    13 年前
    1. 在自己的线程中运行不受信任的代码。例如,这可以防止无限循环等问题,并使未来的步骤更加容易。让主线程等待线程完成,如果花费的时间太长,请用thread.stop终止它。thread.stop不推荐使用,但由于不受信任的代码不应该访问任何资源,因此可以安全地杀死它。

    2. 设置一个 SecurityManager 在那条线上。创建SecurityManager的子类,该子类重写 checkPermission(Permission perm) 简单地扔一个 SecurityException 对于除少数权限外的所有权限。这里列出了方法及其所需的权限: Permissions in the Java TM 6 SDK .

    3. 使用自定义类加载器加载不受信任的代码。类加载器将为不受信任的代码使用的所有类调用,因此您可以执行诸如禁用对单个JDK类的访问之类的操作。要做的是有一个允许JDK类的白名单。

    4. 您可能希望在单独的JVM中运行不受信任的代码。虽然前面的步骤可以确保代码的安全,但是隔离的代码仍然可以做一件烦人的事情:尽可能多地分配内存,这会导致主应用程序的可见内存增长。

    JSR 121: Application Isolation API Specification 是为解决这个问题而设计的,但不幸的是,它还没有实现。

    这是一个非常详细的话题,我大部分时间都在写这篇文章。

    但不管怎样,有些不完美的代码,可能是错误的(伪)代码:

    类装载器

    class MyClassLoader extends ClassLoader {
      @Override
      public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name is white-listed JDK class) return super.loadClass(name);
        return findClass(name);
      }
      @Override
      public Class findClass(String name) {
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
      }
      private byte[] loadClassData(String name) {
        // load the untrusted class data here
      }
    }
    

    安全管理器

    class MySecurityManager extends SecurityManager {
      private Object secret;
      public MySecurityManager(Object pass) { secret = pass; }
      private void disable(Object pass) {
        if (pass == secret) secret = null;
      }
      // ... override checkXXX method(s) here.
      // Always allow them to succeed when secret==null
    }
    

    螺纹

    class MyIsolatedThread extends Thread {
      private Object pass = new Object();
      private MyClassLoader loader = new MyClassLoader();
      private MySecurityManager sm = new MySecurityManager(pass);
      public void run() {
        SecurityManager old = System.getSecurityManager();
        System.setSecurityManager(sm);
        runUntrustedCode();
        sm.disable(pass);
        System.setSecurityManager(old);
      }
      private void runUntrustedCode() {
        try {
          // run the custom class's main method for example:
          loader.loadClass("customclassname")
            .getMethod("main", String[].class)
            .invoke(null, new Object[]{...});
        } catch (Throwable t) {}
      }
    }
    
        2
  •  18
  •   shsmurfy    15 年前

    显然,这样的计划引发了各种各样的安全问题。Java具有严格的安全框架,但它不是微不足道的。把它搞砸,让一个没有特权的用户访问重要的系统组件的可能性不容忽视。

    除此之外,如果您以源代码的形式使用用户输入,您需要做的第一件事就是将其编译为Java字节码。afiak,这不能在本地完成,所以您需要对javac进行系统调用,并将源代码编译为磁盘上的字节码。 Here's 一个可以作为起点的教程。 编辑 正如我在评论中所学到的,你实际上可以从源头上编译源代码。 javax.tools.JavaCompiler

    一旦拥有了JVM字节码,就可以使用 ClassLoader's defineClass 功能。要为此加载的类设置安全上下文,需要指定 ProtectionDomain . 的最小构造函数 保护域 同时需要代码源和 PermissionCollection . PermissionCollection是这里您主要使用的对象-您可以使用它来指定加载类具有的确切权限。这些权限最终应该由JVM执行 AccessController .

    这里有很多可能的错误点,在实现任何东西之前,您应该非常小心地完全理解每件事情。

        3
  •  10
  •   Lii bob    8 年前

    这个 Java-Sandbox 是一个用有限的权限集执行Java代码的库。 它可以用来只允许访问一组白名单中的类和资源。似乎没有 能够限制对单个方法的访问。它使用带有自定义类加载器的系统,并且 安全管理器来实现这一点。

    我没有使用过它,但它看起来设计得很好,文档也很好。

    @Waqas给出了一个非常有趣的答案,解释了这是如何实现自己的。但是,将这样的安全关键和复杂的代码留给专家更安全。

    请注意,虽然该项目自2013年以来没有更新,但创建者将其描述为“实验性的”。它的主页已经消失,但源代码伪造条目仍然存在。

    改编自项目网站的示例代码:

    SandboxService sandboxService = SandboxServiceImpl.getInstance();
    
    // Configure context 
    SandboxContext context = new SandboxContext();
    context.addClassForApplicationLoader(getClass().getName());
    context.addClassPermission(AccessType.PERMIT, "java.lang.System");
    
    // Whithout this line we get a SandboxException when touching System.out
    context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream");
    
    String someValue = "Input value";
    
    class TestEnvironment implements SandboxedEnvironment<String> {
        @Override
        public String execute() throws Exception {
            // This is untrusted code
            System.out.println(someValue);
            return "Output value";
        }
    };
    
    // Run code in sandbox. Pass arguments to generated constructor in TestEnvironment.
    SandboxedCallResult<String> result = sandboxService.runSandboxed(TestEnvironment.class, 
        context, this, someValue);
    
    System.out.println(result.get());
    
        4
  •  4
  •   Shrikant Havale Manu    9 年前

    好吧,给出任何建议或解决方案都为时已晚,但我仍然面临类似的问题,更注重研究。基本上,我试图为电子学习平台中的Java课程提供编程和自动评价。

    1. 一种方法是,为每个学生创建一个单独的虚拟机(不是JVM),但实际的虚拟机的操作系统配置最少。
    2. 根据您的编程语言为JAVA或库安装JRE,您希望学生在这些机器上编译和执行JRE。

    我知道这听起来是一个相当复杂的任务,但是Oracle虚拟箱已经提供了Java API来动态地创建或克隆虚拟机。 https://www.virtualbox.org/sdkref/index.html (注意,即使是VMware也提供了这样做的API)

    对于Linux发行版的最小规模和配置,您可以在这里参考这个版本。 http://www.slitaz.org/en/ ,

    所以现在如果学生搞砸了或者试图这样做,可能是用内存或者文件系统或者网络,插座,最大限度地他可以损坏自己的虚拟机。

    此外,在这些VM内部,您可以提供额外的安全性,比如Java的沙箱(安全管理器)或在Linux上创建用户特定帐户,从而限制访问。

    希望这有帮助!!

        5
  •  2
  •   Arno Unkrig    10 年前

    下面是一个线程安全的问题解决方案:

    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
  •  2
  •   alphaloop    10 年前

    解决公认答案中的问题 SecurityManager 将应用于JVM中的所有线程,而不是基于每个线程,您可以创建一个自定义的 安全管理器 可以为特定线程启用/禁用,如下所示:

    import java.security.Permission;
    
    public class SelectiveSecurityManager extends SecurityManager {
    
      private static final ToggleSecurityManagerPermission TOGGLE_PERMISSION = new ToggleSecurityManagerPermission();
    
      ThreadLocal<Boolean> enabledFlag = null;
    
      public SelectiveSecurityManager(final boolean enabledByDefault) {
    
        enabledFlag = new ThreadLocal<Boolean>() {
    
          @Override
          protected Boolean initialValue() {
            return enabledByDefault;
          }
    
          @Override
          public void set(Boolean value) {
            SecurityManager securityManager = System.getSecurityManager();
            if (securityManager != null) {
              securityManager.checkPermission(TOGGLE_PERMISSION);
            }
            super.set(value);
          }
        };
      }
    
      @Override
      public void checkPermission(Permission permission) {
        if (shouldCheck(permission)) {
          super.checkPermission(permission);
        }
      }
    
      @Override
      public void checkPermission(Permission permission, Object context) {
        if (shouldCheck(permission)) {
          super.checkPermission(permission, context);
        }
      }
    
      private boolean shouldCheck(Permission permission) {
        return isEnabled() || permission instanceof ToggleSecurityManagerPermission;
      }
    
      public void enable() {
        enabledFlag.set(true);
      }
    
      public void disable() {
        enabledFlag.set(false);
      }
    
      public boolean isEnabled() {
        return enabledFlag.get();
      }
    
    }
    

    ToggleSecurirtyManagerPermission 只是一个简单的实现 java.security.Permission 确保只有授权代码才能启用/禁用安全管理器。看起来是这样的:

    import java.security.Permission;
    
    public class ToggleSecurityManagerPermission extends Permission {
    
      private static final long serialVersionUID = 4812713037565136922L;
      private static final String NAME = "ToggleSecurityManagerPermission";
    
      public ToggleSecurityManagerPermission() {
        super(NAME);
      }
    
      @Override
      public boolean implies(Permission permission) {
        return this.equals(permission);
      }
    
      @Override
      public boolean equals(Object obj) {
        if (obj instanceof ToggleSecurityManagerPermission) {
          return true;
        }
        return false;
      }
    
      @Override
      public int hashCode() {
        return NAME.hashCode();
      }
    
      @Override
      public String getActions() {
        return "";
      }
    
    }
    
        7
  •  0
  •   Kieron    15 年前

    您可能需要使用自定义 SecurityManger 和/或 AccessController . 有关详细信息,请参见 Java Security Architecture other security documentation 来自太阳。