Comment créer un sandbox Java?

Je veux faire mon application pour exécuter le code d’autres personnes, aka plugins. Cependant, quelles options dois-je faire pour que cela soit sécurisé afin qu’ils n’écrivent pas de code malveillant. Comment puis-je contrôler ce qu’ils peuvent ou ne peuvent pas faire?

Je suis tombé sur le fait que JVM a une fonctionnalité “sandbox intégré” – qu’est-ce que c’est et est-ce la seule façon? Existe-t-il des bibliothèques Java tierces pour créer un sandbox?

Quelles sont mes options? Des liens vers des guides et des exemples sont appréciés!

Vous recherchez un responsable de la sécurité . Vous pouvez restreindre les permissions d’une application en spécifiant une stratégie .

  • Définir et enregistrer votre propre gestionnaire de sécurité vous permettra de limiter les actions du code – consultez la documentation d’Oracle pour SecurityManager .

  • En outre, envisagez de créer un mécanisme distinct pour charger le code, c.-à-d. Que vous pourriez écrire ou instancier un autre chargeur de classe pour charger le code à partir d’un endroit spécial. Vous pouvez avoir une convention pour charger le code – par exemple depuis un répertoire spécial ou depuis un fichier zip spécialement formaté (fichiers WAR et fichiers JAR). Si vous écrivez un classloader, cela vous oblige à travailler pour obtenir le code chargé. Cela signifie que si vous voyez quelque chose (ou une dépendance) que vous souhaitez rejeter, vous pouvez simplement ne pas charger le code. http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

Jetez un coup d’œil au projet java-sandbox qui permet de créer facilement des sandbox très flexibles pour exécuter du code non fiable.

Pour une application AWT / Swing, vous devez utiliser une classe AppContext non standard, qui peut changer à tout moment. Donc, pour être efficace, vous devez lancer un autre processus pour exécuter le code du plug-in et gérer la communication entre les deux (un peu comme Chrome). Le processus de plug-in nécessite un ensemble SecurityManager et un ClassLoader pour isoler le code du plug-in et appliquer un ProtectionDomain approprié aux classes de plug-in.

Voici comment résoudre le problème avec SecurityManager:

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. * 

* To 'execute through a class' means that the execution stack includes the class. Eg, 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 intersection of the three {@link Permissions} apply. *

* Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any * attempts (eg of the confined class itself) to release the confinement. *

* Code example: *

 * Runnable unprivileged = new Runnable() { * public void run() { * System.getProperty("user.dir"); * } * }; * * // Run without confinement. * unprivileged.run(); // Works fine. * * // Set the most ssortingct 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(); * 

*/ public final class Sandbox { private Sandbox() {} private static final Map, AccessControlContext> CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap, AccessControlContext>()); private static final Map CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap()); private static final Map CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap()); 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. }

La discussion sur cette question m’a incité à démarrer mon propre projet de bac à sable.

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

J’ai rencontré une question de sécurité importante: “Comment autoriser le code en dehors du sandbox à contourner le SecurityManager ?”

Je mets le code sandbox dans son propre groupe ThreadGroup et accorde toujours la permission en dehors de ce groupe. Si vous devez quand même exécuter du code privilégié dans ce groupe (dans un rappel, par exemple), vous pouvez utiliser un ThreadLocal pour définir un indicateur uniquement pour ce thread. Le chargeur de classes empêchera le sandbox d’accéder à ThreadLocal. De plus, si vous faites cela, vous devez interdire l’utilisation des finaliseurs, car ils s’exécutent dans un thread dédié en dehors du ThreadGroup.