Uruchom niezaufany kod we własnym wątku. Na przykład zapobiega to problemom z nieskończonymi pętlami i tym podobnymi, a także ułatwia przyszłe kroki. Niech główny wątek zaczeka na zakończenie wątku, a jeśli trwa to zbyt długo, zabij go za pomocą Thread.stop. Thread.stop jest przestarzały, ale ponieważ niezaufany kod nie powinien mieć dostępu do żadnych zasobów, bezpieczne byłoby jego zabicie.
Ustaw SecurityManager w tym wątku. Utwórz podklasę SecurityManager, która zastępuje checkPermission (uprawnienie Permission), aby po prostu zgłosić SecurityException dla wszystkich uprawnień z wyjątkiem kilku wybranych. Oto lista metod i wymaganych przez nie uprawnień: Uprawnienia w Java TM 6 SDK .
Użyj niestandardowego ClassLoadera, aby załadować niezaufany kod. Twój program ładujący klasy zostałby wywołany dla wszystkich klas, których używa niezaufany kod, więc możesz zrobić takie rzeczy, jak wyłączenie dostępu do poszczególnych klas JDK. Trzeba tylko mieć białą listę dozwolonych klas JDK.
Możesz chcieć uruchomić niezaufany kod w oddzielnej JVM. Podczas gdy poprzednie kroki sprawiłyby, że kod byłby bezpieczny, jest jedna irytująca rzecz, którą izolowany kod może nadal zrobić: alokować jak najwięcej pamięci, co powoduje, że widoczny ślad głównej aplikacji rośnie.
JSR 121: Specyfikacja API izolacji aplikacji została zaprojektowana, aby rozwiązać ten problem, ale niestety nie ma jeszcze implementacji.
Jest to dość szczegółowy temat i głównie piszę to wszystko od początku do końca.
Tak czy inaczej, jakiś niedoskonały, używany na własne ryzyko, prawdopodobnie błędny (pseudo) kod:
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) {
}
}
Menadżer ochrony
class MySecurityManager extends SecurityManager {
private Object secret;
public MySecurityManager(Object pass) { secret = pass; }
private void disable(Object pass) {
if (pass == secret) secret = null;
}
}
Wątek
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
spowoduje problemy w kodzie biblioteki Java. Podobnie kod biblioteki Java będzie wymagał uprawnień. Znacznie lepiej pozwolić naSecurityManager
użyciejava.security.AccessController
. Program ładujący klasy powinien prawdopodobnie również umożliwiać dostęp do własnych klas kodu użytkownika.System.setSecurityManager(…)
wpłynie to na całą maszynę JVM, a nie tylko na wątek wywołujący tę metodę, pomysł podejmowania decyzji dotyczących bezpieczeństwa na podstawie wątku został porzucony, gdy Java przełączyła się z wersji 1.0 na 1.1. W tym czasie uznano, że niezaufany kod może wywołać zaufany kod i odwrotnie, niezależnie od tego, który wątek wykonuje kod. Żaden programista nie powinien powtórzyć tego błędu.Oczywiście taki schemat budzi różnego rodzaju obawy dotyczące bezpieczeństwa. Java ma rygorystyczne ramy bezpieczeństwa, ale nie jest to trywialne. Nie należy przeoczyć możliwości jej zepsucia i umożliwienia nieuprzywilejowanemu użytkownikowi dostępu do ważnych elementów systemu.
Pomijając to ostrzeżenie, jeśli przyjmujesz dane wejściowe użytkownika w postaci kodu źródłowego, pierwszą rzeczą, którą musisz zrobić, jest skompilowanie go do kodu bajtowego Java. AFIAK, nie można tego zrobić natywnie, więc będziesz musiał wykonać wywołanie systemowe javac i skompilować kod źródłowy do kodu bajtowego na dysku. Oto samouczek, który może być użyty jako punkt wyjścia. Edycja : jak dowiedziałem się w komentarzach, możesz skompilować kod Java ze źródła natywnie, używając javax.tools.JavaCompiler
Po uzyskaniu kodu bajtowego maszyny JVM można go załadować do maszyny JVM za pomocą funkcji defineClass programu ClassLoader . Aby ustawić kontekst zabezpieczeń dla tej załadowanej klasy, musisz określić ProtectionDomain . Minimalny konstruktor dla ProtectionDomain wymaga zarówno CodeSource, jak i PermissionCollection . PermissionCollection jest tutaj przedmiotem podstawowego użytku - możesz go użyć do określenia dokładnych uprawnień, jakie ma załadowana klasa. Te uprawnienia powinny być ostatecznie wymuszone przez AccessController maszyny JVM .
Istnieje wiele możliwych błędów i powinieneś być bardzo ostrożny, aby całkowicie zrozumieć wszystko, zanim cokolwiek zaimplementujesz.
źródło
Java-Sandbox jest biblioteką do wykonywania kodu Java z ograniczonego zestawu uprawnień. Można go użyć, aby umożliwić dostęp tylko do zestawu klas i zasobów z białej listy. Wydaje się, że nie jest w stanie ograniczyć dostępu do poszczególnych metod. Aby to osiągnąć, wykorzystuje system z niestandardowym programem ładującym klasy i menedżerem bezpieczeństwa.
Nie używałem go, ale wygląda na dobrze zaprojektowany i dość dobrze udokumentowany.
@waqas udzielił bardzo interesującej odpowiedzi wyjaśniającej, jak można to zrealizować samodzielnie. Ale znacznie bezpieczniej jest pozostawić taki krytyczny i złożony kod z punktu widzenia bezpieczeństwa ekspertom.
Zwróć jednak uwagę, że projekt nie był aktualizowany od 2013 roku, a twórcy określają go jako „eksperymentalny”. Jego strona główna zniknęła, ale wpis Source Forge pozostał.
Przykładowy kod zaadaptowany ze strony internetowej projektu:
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());
źródło
Aby rozwiązać problem w zaakceptowanej odpowiedzi, zgodnie z którą niestandardowy
SecurityManager
będzie miał zastosowanie do wszystkich wątków w JVM, a nie na podstawie poszczególnych wątków, możesz utworzyć niestandardowy,SecurityManager
który można włączyć / wyłączyć dla określonych wątków w następujący sposób: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
to tylko prosta implementacjajava.security.Permission
zapewniająca, że tylko autoryzowany kod może włączać / wyłączać menedżera bezpieczeństwa. To wygląda tak: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 ""; } }
źródło
Cóż, jest już bardzo późno na sugestie lub rozwiązania, ale wciąż miałem do czynienia z podobnym problemem, bardziej ukierunkowanym na badania. Zasadniczo próbowałem zapewnić zabezpieczenie i automatyczne oceny zadań programistycznych dla kursu Java na platformach e-learningowych.
Wiem, że brzmi to dość skomplikowanie i wymaga wielu zadań, ale Oracle Virtual Box już zapewnia Java API do dynamicznego tworzenia lub klonowania maszyn wirtualnych. https://www.virtualbox.org/sdkref/index.html (Uwaga, nawet VMware zapewnia również API do robienia tego samego)
Aby uzyskać informacje o minimalnym rozmiarze i konfiguracji dystrybucji Linuksa, możesz odwołać się do tej tutaj http://www.slitaz.org/en/ ,
Więc teraz, jeśli uczniowie coś zepsują lub spróbują to zrobić, może mieć pamięć, system plików lub sieć, gniazdo, maksymalnie może uszkodzić własną maszynę wirtualną.
Również wewnętrznie w tych maszynach wirtualnych można zapewnić dodatkowe zabezpieczenia, takie jak Sandbox (menedżer bezpieczeństwa) dla języka Java lub tworzenie kont dla użytkowników w systemie Linux, a tym samym ograniczanie dostępu.
Mam nadzieję że to pomoże !!
źródło
Oto bezpieczne wątkowo rozwiązanie problemu:
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. }
Proszę skomentuj!
CU
Arno
źródło
Prawdopodobnie będziesz musiał użyć niestandardowego SecurityManger i / lub AccessController . Więcej szczegółowych informacji można znaleźć w dokumentacji Java Security Architecture i innej dokumentacji dotyczącej zabezpieczeń firmy Sun.
źródło