¿Cómo creo un entorno limitado de Java?

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?

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

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

Usted está buscando un gerente de seguridad . Puede restringir los permisos de una aplicación especificando una política .

  • La definición y el registro de su propio administrador de seguridad le permitirán limitar lo que hace el código: consulte la documentación de Oracle para SecurityManager .

  • Además, considere crear 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

Eche un vistazo al proyecto java-sandbox que permite crear fácilmente sandboxes muy flexibles para ejecutar código no confiable.

Para una aplicación AWT / Swing necesita usar una clase AppContext no estándar, 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 de SecurityManager y un ClassLoader para aislar el código del complemento y aplicar un ProtectionDomain apropiado para las clases de complemento.

Así es como se puede resolver el problema 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. * 

* 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 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(); * 

*/ 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 discusión sobre esta pregunta me inspiró a comenzar mi propio proyecto de recinto de seguridad.

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

En él me he encontrado con una importante pregunta de seguridad: “¿Cómo se permite que el código fuera de la zona de pruebas evite SecurityManager ?”

Puse el código de la caja de arena en su propio ThreadGroup, y siempre otorgo permiso cuando estoy fuera de ese grupo. Si necesita ejecutar código privilegiado en ese grupo de todos modos (en una callback, 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.

    Intereting Posts