Comment créer un bac à sable Java?
je veux faire mon application pour exécuter le code d'autres personnes, alias plugins. Cependant, quelles options dois-je faire pour rendre ce 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?
j'ai trébuché sur le fait que JVM dispose d'une fonction "built in sandbox" - qu'est-ce que c'est et est-ce la seule façon? Tiers de bibliothèques Java pour faire un bac à sable?
quelles options ai-je? Les liens vers des guides et des exemples sont appréciés!
6 réponses
vous recherchez un responsable de la sécurité. Vous pouvez restreindre les permissions d'une application en spécifiant un politique.
définir et enregistrer votre propre gestionnaire de sécurité vous permettra de limiter ce que le code fait - voir la documentation d'oracle pour SecurityManager.
pensez Aussi création d'un mécanisme séparé pour charger le code - i.e. vous pouvez écrire ou instancier un autre Classloader pour charger le code à partir d'un endroit spécial. Vous pouvez avoir une convention pour charger le code - par exemple à partir d'un répertoire spécial ou à partir d'un fichier zip spécialement formaté (sous forme de fichiers de guerre et de fichiers JAR). Si vous écrivez un classloader, il vous met dans la position de devoir faire du travail pour obtenir le code chargé. Cela signifie que si vous voyez quelque chose (ou une dépendance) que vous voulez rejeter, vous pouvez tout simplement ne pas charger le code. http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html
regardez java-sandbox projet qui permet de créer facilement des bacs à sable très flexibles pour exécuter du code non fiable.
Pour un AWT/Swing application dont vous avez besoin pour une utilisation non-standard AppContext
la classe, qui peut changer à tout moment. Donc, pour être efficace, vous devez lancer un autre processus pour exécuter du code plug-in, et gérer la communication entre les deux (un peu comme Chrome). Le processus de plug-in aura besoin d'un SecurityManager
et un ClassLoader
pour isoler le code plug-in et appliquer unProtectionDomain
plug-dans les classes.
Voici comment le problème peut être résolu 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
j'y ai rencontré une importante question de sécurité: "Comment permettre au code à l'extérieur du bac à sable de contourner le SecurityManager
?"
j'ai mis le code du bac à sable dans son propre ThreadGroup, et j'accorde toujours la permission En dehors de ce groupe. Si vous avez besoin d'exécuter du code privilégié dans ce groupe de toute façon (dans un rappel, par exemple), vous pouvez utiliser un ThreadLocal pour définir un indicateur de ce Thread. Le classloader empêchera le bac à sable d'accéder au ThreadLocal. En outre, si vous faites cela, vous devez interdire l'utilisation de finaliseurs, car ils fonctionnent dans un thread dédié en dehors du ThreadGroup.