Запустите ненадежный код в отдельном потоке. Это, например, предотвращает проблемы с бесконечными циклами и т. Д. И упрощает дальнейшие шаги. Попросите основной поток дождаться завершения потока, а если занимает слишком много времени, убейте его с помощью Thread.stop. Thread.stop устарел, но поскольку ненадежный код не должен иметь доступа к каким-либо ресурсам, его можно безопасно убить.
Установите SecurityManager в этом потоке. Создайте подкласс SecurityManager, который переопределяет checkPermission (Permission perm), чтобы просто выбросить SecurityException для всех разрешений, кроме нескольких избранных. Здесь есть список методов и необходимых разрешений: Разрешения в Java TM 6 SDK .
Используйте собственный ClassLoader для загрузки ненадежного кода. Ваш загрузчик классов будет вызываться для всех классов, которые использует ненадежный код, поэтому вы можете делать такие вещи, как отключение доступа к отдельным классам JDK. Что нужно сделать, это создать белый список разрешенных классов JDK.
Возможно, вы захотите запустить ненадежный код в отдельной JVM. Хотя предыдущие шаги сделали бы код безопасным, есть одна неприятная вещь, которую изолированный код все еще может делать: выделять как можно больше памяти, что приводит к увеличению видимого следа основного приложения.
JSR 121: Спецификация API изоляции приложений была разработана для решения этой проблемы, но, к сожалению, еще не реализована.
Это довольно подробная тема, и я в основном пишу все это не в своей голове.
Но в любом случае какой-то несовершенный, рискованный, возможно, ошибочный (псевдо) код:
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) {
}
}
Менеджер по безопасности
class MySecurityManager extends SecurityManager {
private Object secret;
public MySecurityManager(Object pass) { secret = pass; }
private void disable(Object pass) {
if (pass == secret) 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 {
loader.loadClass("customclassname")
.getMethod("main", String[].class)
.invoke(null, new Object[]{...});
} catch (Throwable t) {}
}
}
Thread.stop
вызовет проблемы в коде библиотеки Java. Точно так же для кода библиотеки Java потребуются разрешения. Намного лучше позволитьSecurityManager
использоватьjava.security.AccessController
. Загрузчик классов, вероятно, также должен разрешать доступ к собственным классам пользовательского кода.System.setSecurityManager(…)
это повлияет на всю JVM, а не только на поток, вызывающий этот метод, идея принятия решений безопасности на основе потока была оставлена, когда Java переключилась с 1.0 на 1.1. В это время было признано, что ненадежный код может вызывать доверенный код и наоборот, независимо от того, какой поток выполняет код. Ни один разработчик не должен повторять ошибку.Очевидно, что такая схема вызывает всевозможные проблемы безопасности. Java имеет строгую структуру безопасности, но это нетривиально. Нельзя упускать из виду возможность облажаться и позволить непривилегированному пользователю получить доступ к жизненно важным компонентам системы.
Помимо этого предупреждения, если вы вводите пользовательский ввод в виде исходного кода, первое, что вам нужно сделать, это скомпилировать его в байт-код Java. AFIAK, это невозможно сделать изначально, поэтому вам нужно сделать системный вызов javac и скомпилировать исходный код в байт-код на диске. Вот руководство, которое можно использовать в качестве отправной точки для этого. Изменить : как я узнал в комментариях, вы действительно можете скомпилировать код Java из исходного кода с помощью javax.tools.JavaCompiler
Если у вас есть байт-код JVM, вы можете загрузить его в JVM с помощью функции defineClass в ClassLoader . Чтобы установить контекст безопасности для этого загруженного класса, вам необходимо указать ProtectionDomain . Минимальный конструктор для ProtectionDomain требует как CodeSource, так и PermissionCollection . PermissionCollection - это объект, который вы здесь в первую очередь используете - вы можете использовать его, чтобы указать точные разрешения, которые имеет загруженный класс. Эти разрешения должны быть в конечном итоге обеспечены AccessController JVM .
Здесь много возможных ошибок, и вы должны быть очень осторожны, чтобы полностью понять все, прежде чем что-либо реализовывать.
источник
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());
источник
Чтобы решить проблему в принятом ответе, в соответствии с которым настройка
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 ""; } }
источник
Что ж, уже очень поздно давать какие-либо предложения или решения, но все же я столкнулся с подобной проблемой, более ориентированной на исследования. В основном я пытался обеспечить предоставление и автоматическую оценку заданий по программированию для курса Java на платформах электронного обучения.
Я знаю, что это звучит довольно сложно и требует большого количества задач, но Oracle Virtual Box уже предоставляет Java API для динамического создания или клонирования виртуальных машин. https://www.virtualbox.org/sdkref/index.html (обратите внимание, даже VMware также предоставляет API для того же)
А для получения информации о минимальном размере и конфигурации дистрибутива Linux вы можете обратиться к этому здесь http://www.slitaz.org/en/ ,
Итак, теперь, если ученик испортит или попытается это сделать, может быть, с памятью, файловой системой или сетью, сокетом, максимум он может повредить свою собственную виртуальную машину.
Также внутри этих виртуальных машин вы можете обеспечить дополнительную безопасность, такую как Sandbox (менеджер безопасности) для Java или создать учетные записи для конкретных пользователей в Linux и, таким образом, ограничить доступ.
Надеюсь это поможет !!
источник
Вот поточно-ориентированное решение проблемы:
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. }
Прокомментируйте, пожалуйста!
CU
Арно
источник
Возможно, вам потребуется использовать собственный SecurityManger и / или AccessController . Для получения дополнительных сведений см. Архитектуру безопасности Java и другую документацию по безопасности от Sun.
источник