针对 Java 应用程序中恶意代码的沙箱
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/502218/
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
Sandbox against malicious code in a Java application
提问by Alan Krueger
In a simulation server environment where users are allowed to submit their own code to be run by the server, it would clearly be advantageous for any user-submitted code to be run in side a sandbox, not unlike Applets are within a browser. I wanted to be able to leverage the JVM itself, rather than adding another VM layer to isolate these submitted components.
在允许用户提交他们自己的代码以供服务器运行的模拟服务器环境中,任何用户提交的代码在沙箱中运行显然是有利的,这与 Applet 在浏览器中不同。我希望能够利用 JVM 本身,而不是添加另一个 VM 层来隔离这些提交的组件。
This kind of limitation appears to be possible using the existing Java sandbox model, but is there a dynamic way to enable that for just the user-submitted parts of a running application?
使用现有的 Java 沙箱模型似乎可以实现这种限制,但是是否有一种动态方法可以仅针对正在运行的应用程序的用户提交部分启用该限制?
采纳答案by waqas
Run the untrusted code in its own thread. This for example prevents problems with infinite loops and such, and makes the future steps easier. Have the main thread wait for the thread to finish, and if takes too long, kill it with Thread.stop. Thread.stop is deprecated, but since the untrusted code shouldn't have access to any resources, it would be safe to kill it.
Set a SecurityManageron that Thread. Create a subclass of SecurityManager which overrides checkPermission(Permission perm)to simply throw a SecurityExceptionfor all permissions except a select few. There's a list of methods and the permissions they require here: Permissions in the JavaTM6 SDK.
Use a custom ClassLoader to load the untrusted code. Your class loader would get called for all classes which the untrusted code uses, so you can do things like disable access to individual JDK classes. The thing to do is have a white-list of allowed JDK classes.
You might want to run the untrusted code in a separate JVM. While the previous steps would make the code safe, there's one annoying thing the isolated code can still do: allocate as much memory as it can, which causes the visible footprint of the main application to grow.
在其自己的线程中运行不受信任的代码。例如,这可以防止无限循环等问题,并使以后的步骤更容易。让主线程等待线程完成,如果时间过长,用 Thread.stop 杀死它。Thread.stop 已被弃用,但由于不受信任的代码不应访问任何资源,因此杀死它是安全的。
在该线程上设置一个SecurityManager。创建一个 SecurityManager 的子类,它覆盖checkPermission(Permission perm)以简单地为除少数权限之外的所有权限抛出SecurityException。此处列出了方法及其所需的权限:Java TM6 SDK中的权限。
使用自定义 ClassLoader 加载不受信任的代码。您的类加载器将被不受信任的代码使用的所有类调用,因此您可以执行诸如禁用对单个 JDK 类的访问等操作。要做的事情是有一个允许的 JDK 类的白名单。
您可能希望在单独的 JVM 中运行不受信任的代码。虽然前面的步骤可以使代码安全,但隔离代码仍然可以做一件烦人的事情:分配尽可能多的内存,这会导致主应用程序的可见占用空间增加。
JSR 121: Application Isolation API Specificationwas designed to solve this, but unfortunately it doesn't have an implementation yet.
JSR 121:应用程序隔离 API 规范旨在解决这个问题,但遗憾的是它还没有实现。
This is a pretty detailed topic, and I'm mostly writing this all off the top of my head.
这是一个非常详细的主题,我主要是在脑子里写这些。
But anyway, some imperfect, use-at-your-own-risk, probably buggy (pseudo) code:
但无论如何,一些不完美的,使用风险自负,可能有问题的(伪)代码:
ClassLoader
类加载器
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
}
}
SecurityManager
安全管理器
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
}
Thread
线
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) {}
}
}
回答by Kieron
You will probably need to use a custom SecurityMangerand/or AccessController. For lots of detail, see Java Security Architectureand other security documentationfrom Sun.
您可能需要使用自定义SecurityManger和/或AccessController。有关更多详细信息,请参阅Sun 的Java 安全体系结构和其他安全文档。
回答by shsmurfy
Obviously such a scheme raises all sorts of security concerns. Java has a rigorous security framework, but it isn't trivial. The possibility of screwing it up and letting an unprivileged user access vital system components shouldn't be overlooked.
显然,这样的方案会引起各种安全问题。Java 有一个严格的安全框架,但它并非微不足道。不应忽视将其搞砸并让非特权用户访问重要系统组件的可能性。
That warning aside, if you're taking user input in the form of source code, the first thing you need to do is compile it to Java bytecode. AFIAK, this cannot be done natively, so you'll need to make a system call to javac, and compile the source code to bytecode on disk. Here'sa tutorial that can be used as a starting point for this. Edit: as I learned in the comments, you actually can compile Java code from source natively using javax.tools.JavaCompiler
撇开这个警告不谈,如果您以源代码的形式获取用户输入,您需要做的第一件事就是将其编译为 Java 字节码。AFIAK,这不能在本地完成,因此您需要对 javac 进行系统调用,并将源代码编译为磁盘上的字节码。这是一个教程,可用作此操作的起点。 编辑:正如我在评论中了解到的,您实际上可以使用javax.tools.JavaCompiler从本机源代码编译 Java 代码
Once you have JVM bytecode, you can load it into the JVM using a ClassLoader'sdefineClassfunction. To set a security context for this loaded class you will need to specify a ProtectionDomain. The minimal constructor for a ProtectionDomainrequires both a CodeSource and a PermissionCollection. The PermissionCollection is the object of primary use to you here- you can use it to specify the exact permissions the loaded class has. These permissions should be ultimately enforced by the JVM's AccessController.
一旦有了 JVM 字节码,就可以使用ClassLoader 的defineClass函数将其加载到 JVM 中。要为此加载的类设置安全上下文,您需要指定ProtectionDomain。ProtectionDomain的最小构造函数需要 CodeSource 和PermissionCollection。PermissionCollection 是您在这里主要使用的对象 - 您可以使用它来指定加载的类具有的确切权限。这些权限最终应由 JVM 的AccessController强制执行。
There's a lot of possible points of error here, and you should be extremely careful to completely understand everything before you implement anything.
这里有很多可能的错误点,在实施任何事情之前,您应该非常小心地完全理解所有内容。
回答by Lii
The Java-Sandboxis a library for executing Java code with a limited set of permissions. It can be used to allow access to only a set of white-listed classes and resources. It doesn't seem to be able to restrict access to individual methods. It uses a system with a custom class loader and security manager to achieve this.
的Java的沙箱是用于执行Java代码与一组有限的权限的文库。它可用于仅允许访问一组列入白名单的类和资源。它似乎无法限制对单个方法的访问。它使用带有自定义类加载器和安全管理器的系统来实现这一点。
I have not used it but it looks well designed and reasonably well documented.
我没有使用过它,但它看起来设计得很好,而且文档也相当齐全。
@waqas has given a very interesting answer explaining how this is possible to implement yourself. But it is much safer to leave such security critical and complex code to experts.
@waqas 给出了一个非常有趣的答案,解释了如何实现自己。但是,将此类安全关键且复杂的代码留给专家要安全得多。
Notice though that the project has not been updated since 2013 and the the creators describe it as "experimental". Its home page has disappeared but the Source Forge entry remains.
请注意,该项目自 2013 年以来一直没有更新,创建者将其描述为“实验性”。它的主页已经消失,但 Source Forge 条目仍然存在。
Example code adapted from the project web site:
从项目网站改编的示例代码:
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());
回答by Arno Unkrig
Here's a thread-safe solution for the problem:
这是该问题的线程安全解决方案:
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.
}
Please comment!
请给出意见!
CU
CU
Arno
阿诺
回答by alphaloop
To address the problem in the accepted answer whereby the custom SecurityManagerwill apply to all threads in the JVM, rather than on a per-thread basis, you can create a custom SecurityManagerthat can be enabled/disabled for specific threads as follows:
要解决已接受的答案中的问题,即自定义SecurityManager将应用于 JVM 中的所有线程,而不是在每个线程的基础上,您可以创建一个SecurityManager可以为特定线程启用/禁用的自定义,如下所示:
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();
}
}
ToggleSecurirtyManagerPermissionis just a simple implementation of java.security.Permissionto ensure that only authorised code can enable/disable the security manager. It looks like this:
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 "";
}
}
回答by Shrikant Havale
Well it's very late to give any suggestions or solutions, but still I was facing similar kind of issue, kind of more research oriented. Basically I was trying to provide a provision and automatic evaluations for programming assignments for Java course in e-learning platforms.
好吧,现在给出任何建议或解决方案已经很晚了,但我仍然面临着类似的问题,更偏向于研究。基本上,我试图为电子学习平台中的 Java 课程的编程作业提供规定和自动评估。
- one way could be, Create a separate virtual machines (not JVM) but actual virtual machines with minimum configuration possible OS for each of student.
- Install JRE for Java or libraries according to your programming languages, whichever you want students to compile and execute on these machines.
- 一种方法可能是,为每个学生创建一个单独的虚拟机(不是 JVM),而是具有最低配置可能的操作系统的实际虚拟机。
- 根据您的编程语言安装 JRE for Java 或库,无论您希望学生在这些机器上编译和执行哪个。
I know this sounds a quite complex and lot of tasks, but Oracle Virtual Box already provides Java API to create or clone virtual machines dynamically. https://www.virtualbox.org/sdkref/index.html(Note, even VMware also provides API for doing same)
我知道这听起来很复杂而且任务很多,但是 Oracle Virtual Box 已经提供了 Java API 来动态创建或克隆虚拟机。 https://www.virtualbox.org/sdkref/index.html(注意,即使是 VMware 也提供 API 来做同样的事情)
And for the minimum size and configuration Linux distribution you can refer to this one here http://www.slitaz.org/en/,
对于最小大小和配置的 Linux 发行版,您可以在http://www.slitaz.org/en/ 上参考这个,
So now if students messes up or tries to do it, may be with memory or file system or networking, socket, maximum he can damage his own VM.
所以现在如果学生搞砸了或试图这样做,可能是内存或文件系统或网络、套接字,最多他可以损坏自己的虚拟机。
Also internally into these VM's you can provide additional security like Sandbox (security manager ) for Java or creating user specific accounts on Linux and thus restricting access.
同样在这些 VM 内部,您可以提供额外的安全性,例如 Java 的 Sandbox(安全管理器)或在 Linux 上创建用户特定帐户,从而限制访问。
Hope this helps !!
希望这可以帮助 !!

