2009-11-11 13 views
40

Quiero hacer que mi aplicación ejecute el código de otras personas, también conocido como complementos. Sin embargo, ¿qué opciones tengo para hacer esto seguro para que no escriban código malicioso? ¿Cómo controlo lo que pueden o no pueden hacer?¿Cómo creo un entorno limitado de Java?

He tropezado con que la JVM tiene una función de "caja de arena incorporada": ¿qué es y es esta la única manera? ¿Hay bibliotecas de Java de terceros para crear un recinto de seguridad?

¿Qué opciones tengo? Enlaces a guías y ejemplos son apreciados!

Respuesta

19
  • Definir y registrar su propio controlador de seguridad le permitirá limitar lo que hace el código - Consulte la documentación de Oracle para SecurityManager.

  • Además, considere creando un mecanismo separado para cargar el código, es decir, podría escribir o instanciar otro Classloader para cargar el código desde un lugar especial. Es posible que tenga una convención para cargar el código, por ejemplo, desde un directorio especial o desde un archivo zip especialmente formateado (como archivos WAR y archivos JAR). Si está escribiendo un cargador de clases, lo pone en la posición de tener que trabajar para que se cargue el código. Esto significa que si ve algo (o alguna dependencia) que desea rechazar, simplemente puede dejar de cargar el código. http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

4

Para una aplicación de AWT/Swing es necesario utilizar no estándar de clase AppContext, lo que podría cambiar en cualquier momento. Entonces, para ser efectivo necesitaría comenzar otro proceso para ejecutar el código del complemento, y tratar con la comunicación entre los dos (un poco como Chrome). El proceso de complemento necesitará un conjunto SecurityManager y un ClassLoader para aislar el código del complemento y aplicar un ProtectionDomain adecuado a las clases de complemento.

5

Eche un vistazo a the java-sandbox project que permite crear fácilmente sandboxes muy flexibles para ejecutar el código que no es de confianza.

+1

Gracias por publicar esa biblioteca, hace que algo en lo que estoy trabajando sea mucho más fácil. –

+0

El enlace está muerto. Google encontró [esto] (https://sourceforge.net/projects/javasandboxlibrary/), ¿es lo mismo? – planetguy32

+0

@Arno Mittelbach no está disponible ahora – Sheldon

3

Así es como el problema puede ser resuelto con un 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. 
* <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. 

} 
0

La discusión sobre esta cuestión me inspiró para poner en marcha mi propio proyecto arenero.

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

En lo que he encontrado una importante pregunta de seguridad: "¿Cómo permitir que el código fuera del entorno limitado a pasar por alto el SecurityManager?"

Pongo el código Sandbox en su propio ThreadGroup, y siempre otorgo permiso cuando está fuera de ese grupo. Si necesita ejecutar código privilegiado en ese grupo de todos modos (en una devolución de llamada, por ejemplo), puede usar un ThreadLocal para establecer un indicador solo para ese subproceso. El cargador de clases evitará que el sandbox acceda a ThreadLocal. Además, si hace esto, debe prohibir el uso de finalizadores, ya que se ejecutan en un hilo dedicado fuera del ThreadGroup.

Cuestiones relacionadas