Je veux faire mon application pour exécuter le code d'autres personnes, aka plugins. Cependant, quelles sont les options dont je dispose pour sécuriser ce lieu afin qu’ils n’écrivent pas de code malveillant. Comment puis-je contrôler ce qu'ils peuvent ou ne peuvent pas faire?
J'ai trouvé par hasard que la machine virtuelle Java dispose d'une fonctionnalité "construit dans le bac à sable" - qu'est-ce que c'est et est-ce le seul moyen? Existe-t-il des bibliothèques Java tierces pour créer un bac à sable?
Quelles sont mes options? Les liens vers des guides et des exemples sont appréciés!
Vous recherchez un responsable de la sécurité . Vous pouvez restreindre les autorisations d'une application en spécifiant un policy .
Définir et enregistrer votre propre gestionnaire de sécurité} _ vous permettra de limiter le code - voir la documentation Oracle pour SecurityManager .
Considérez également créant un mécanisme distinct pour le chargement du code - vous pouvez écrire ou instancier un autre chargeur de classe} pour charger le code depuis un emplacement spécial. Vous pouvez avoir une convention pour le chargement du code - par exemple à partir d'un répertoire spécial ou d'un fichier Zip spécialement formaté (fichiers WAR et JAR). Si vous écrivez un chargeur de classe, vous serez obligé de travailler pour que le code soit chargé. Cela signifie que si vous voyez quelque chose (ou une dépendance) que vous voulez rejeter, vous pouvez simplement ne pas charger le code. http://Java.Sun.com/javase/6/docs/api/Java/lang/ClassLoader.html
Jetez un oeil sur le 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 être modifiée à tout moment. Donc, pour être efficace, vous devez démarrer un autre processus pour exécuter du code de 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 une ClassLoader
pour isoler le code du plug-in et appliquer une ProtectionDomain
appropriée aux classes de plug-in.
Voici comment résoudre le problème avec un SecurityManager:
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.
}
La discussion sur cette question m'a inspiré pour démarrer mon propre projet de bac à sable.
https://github.com/Black-Mantha/sandbox
Dans celui-ci, je suis tombé sur une question de sécurité importante: "Comment autorisez-vous le code en dehors du bac à sable à contourner la SecurityManager
?"
Je mets le code du bac à sable dans son propre ThreadGroup et accorde toujours l'autorisation en dehors de ce groupe. Si vous devez malgré tout exécuter du code privilégié dans ce groupe (dans un rappel, par exemple), vous pouvez utiliser un ThreadLocal pour définir un indicateur pour ce Thread uniquement. Le chargeur de classe empêchera le bac à sable 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.