在允许用户提交自己的代码以供服务器运行的模拟服务器环境中,任何用户提交的代码都可以在沙箱中运行显然是有利的,这与Applet在浏览器中的运行方式不同。我希望能够利用JVM本身,而不是添加另一个VM层来隔离这些提交的组件。
使用现有的Java沙箱模型似乎可以实现这种限制,但是有一种动态方法可以仅为正在运行的应用程序的用户提交的部分启用吗?
答案 0 :(得分:107)
在自己的线程中运行不受信任的代码。例如,这可以防止无限循环等问题,并使未来的步骤更容易。让主线程等待线程完成,如果花费太长时间,请使用Thread.stop将其终止。不推荐使用Thread.stop,但由于不受信任的代码不能访问任何资源,因此将其删除是安全的。
在该线程上设置SecurityManager。创建一个SecurityManager的子类,它覆盖checkPermission(Permission perm),只为所有权限抛出SecurityException,除了少数几个。这里有一个方法及其所需权限列表:Permissions in the JavaTM 6 SDK。
使用自定义ClassLoader加载不受信任的代码。您的类加载器将被调用所有不受信任的代码使用的类,因此您可以执行诸如禁用对单个JDK类的访问的操作。要做的是有一个允许的JDK类的白名单。
您可能希望在单独的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) {}
}
}
答案 1 :(得分:18)
显然,这样的计划引发了各种各样的安全问题。 Java有一个严格的安全框架,但它并非无足轻重。不应忽视将其搞砸并让非特权用户访问重要系统组件的可能性。
除了警告之外,如果你以源代码的形式接受用户输入,你需要做的第一件事就是将它编译为Java字节码。 AFIAK,这不能在本地完成,因此您需要对javac进行系统调用,并将源代码编译为磁盘上的字节码。 Here's一个可以作为此起点的教程。 编辑:正如我在评论中所了解到的那样,您实际上可以使用javax.tools.JavaCompiler本地编译来自源代码的Java代码
获得JVM字节码后,可以使用ClassLoader's defineClass函数将其加载到JVM中。要为此加载的类设置安全上下文,您需要指定ProtectionDomain。 ProtectionDomain的最小构造函数需要CodeSource和PermissionCollection。 PermissionCollection是您在这里主要使用的对象 - 您可以使用它来指定加载的类具有的确切权限。这些权限最终应由JVM AccessController强制执行。
这里有很多可能的错误点,在实施任何内容之前,你应该非常小心地完全理解所有内容。
答案 2 :(得分:10)
Java-Sandbox是一个用于执行具有有限权限集的Java代码的库。 它可用于仅允许访问一组列入白名单的类和资源。它似乎没有 能够限制对单个方法的访问。它使用一个带有自定义类加载器的系统 安全经理实现这一目标。
我没有使用它,但它设计得很好并且记录得很合理。
@waqas给出了一个非常有趣的答案,解释了如何实现这一点。但是,将这些安全关键和复杂的代码留给专家会更安全。请注意,自2013年以来该项目尚未更新,创作者将其描述为“实验性”。它的主页已经消失,但Source Forge条目仍然存在。
从项目网站改编的示例代码:
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());
答案 3 :(得分:4)
现在提出任何建议或解决方案已经很晚了,但我仍面临类似的问题,更多的研究导向。基本上我试图为电子学习平台中的Java课程编程作业提供一个规定和自动评估。
我知道这听起来非常复杂,但是Oracle Virtual Box已经提供了Java API来动态创建或克隆虚拟机。 https://www.virtualbox.org/sdkref/index.html(注意,即使VMware也提供了相同的API)
对于Linux发行版的最小大小和配置,您可以在此处参考此http://www.slitaz.org/en/,
所以现在如果学生搞砸或试图这样做,可能是内存或文件系统或网络,套接字,最大可能会损坏自己的VM。
此外,在这些VM的内部,您可以为Java提供额外的安全性,例如Sandbox(安全管理器),或者在Linux上创建用户特定帐户,从而限制访问。
希望这会有所帮助!!
答案 4 :(得分:2)
这是针对该问题的线程安全解决方案:
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.
}
请评论!
CU
阿诺
答案 5 :(得分:2)
要解决接受的答案中的问题,即自定义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();
}
}
对特定线程启用/禁用如下:
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 "";
}
}
的一个简单实现,以确保只有授权代码才能启用/禁用安全管理器。它看起来像这样:
{{1}}
答案 6 :(得分:0)
您可能需要使用自定义SecurityManger和/或AccessController。有关详细信息,请参阅Sun的Java Security Architecture和other security documentation。