如何创建 Java 沙箱?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1715036/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 21:49:55  来源:igfitidea点击:

How do I create a Java sandbox?

javasecuritypluginssandbox

提问by corgrath

I want to make my application to run other people's code, aka plugins. However, what options do I have to make this secure so they don't write malicious code. How do I control what they can or can not do?

我想让我的应用程序运行其他人的代码,也就是插件。但是,我有什么选择可以确保安全,这样他们就不会编写恶意代码。我如何控制他们能做什么或不能做什么?

I have stumbled around that JVM has a "built in sandbox" feature - what is it and is this the only way? Are there third-party Java libraries for making a sandbox?

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

What options do I have? Links to guides and examples is appreciated!

我有哪些选择?指南和示例的链接表示赞赏!

采纳答案by tangens

You are looking for a security manager. You can restrict the permissions of an application by specifying a policy.

您正在寻找安全经理。您可以通过指定策略来限制应用程序的权限。

回答by Dafydd Rees

  • Defining and registering your own security managerwill allow you to limit what the code does - see oracle documentation for SecurityManager.

  • Also, consider creating a separate mechanism for loading the code - i.e. you could write or instantiate another Classloaderto load the code from a special place. You might have a convention for loading the code - for example from a special directory or from a specially formatted zip file (as WAR files and JAR files). If you're writing a classloader it puts you in the position of having to do work to get the code loaded. This means that if you see something (or some dependency) you want to reject you can simply fail to load the code. http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

  • 定义并注册自己的安全管理将允许你限制的代码做什么-请参见Oracle文档安全管理器

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

回答by Tom Hawtin - tackline

For an AWT/Swing application you need to use non-standard AppContextclass, which could change at any time. So, to be effective you would need to start another process to run plug-in code, and deal with communication between the two (a little like Chrome). The plug-in process will need a SecurityManagerset and a ClassLoaderto both isolate the plug-in code and apply an appropriate ProtectionDomainto plug-in classes.

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

回答by Arno Mittelbach

Have a look at the java-sandbox projectwhich allows to easily create very flexible sandboxes to run untrusted code.

查看java-sandbox 项目,它允许轻松创建非常灵活的沙箱来运行不受信任的代码。

回答by Arno Unkrig

Here's how the problem can be solved with a SecurityManager:

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

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

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.

}

回答by Black Mantha

The discussion on this question inspired me to start up my own sandbox project.

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

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

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

In it I've come across an important security question: "How do you allow the code outside the sandbox to bypass the SecurityManager?"

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

I put the sandbox code in its own ThreadGroup, and always grant permission when outside that group. If you need to run privileged code in that group anyway (in a callback, for example), you can use a ThreadLocal to set a flag for that Thread only. The classloader will prevent the sandbox from accessing the ThreadLocal. Also, if you do this you need to forbid the use of finalizers, since they run in a dedicated thread outside the ThreadGroup.

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

回答by Arno Unkrig

After spending a day in the depths of the Java security APIs, I found an amazingly simple solution for executing untrusted code within a sandbox confined by Permissions:

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

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

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

Here's the (simplified) source code:

这是(简化的)源代码:

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