2009-02-02 4 views
82

En un entorno de servidor de simulación donde los usuarios se les permite presentar su propio código a ser ejecutado por el servidor, sería claramente ventajoso para cualquier código enviado por el usuario que se ejecute en el lado de una caja de arena , al igual que los Applets están dentro de un navegador. Quería aprovechar la propia JVM, en lugar de agregar otra capa de VM para aislar estos componentes enviados.recinto de seguridad contra código malicioso en una aplicación Java

Este tipo de limitación parece ser posible utilizando el modelo de recinto de seguridad de Java existente, pero hay una manera dinámica para permitir que por sólo las partes enviadas por los usuarios de una aplicación que se ejecuta?

Respuesta

99
  1. Ejecute el código que no es de confianza en su propio hilo. Esto, por ejemplo, previene problemas con bucles infinitos y demás, y facilita los pasos futuros. Haga que el hilo principal espere hasta que termine el hilo, y si tarda demasiado, elimínelo con Thread.stop. Thread.stop está en desuso, pero como el código que no es de confianza no debería tener acceso a ningún recurso, sería seguro matarlo.

  2. Conjunto un SecurityManager en ese hilo. Cree una subclase de SecurityManager que anule checkPermission(Permission perm) para simplemente lanzar un SecurityException para todos los permisos excepto unos pocos. Hay una lista de métodos y los permisos que requieren aquí: Permissions in the JavaTM 6 SDK.

  3. Utilice un cargador de clases a medida para cargar el código no confiable. Su cargador de clases recibiría llamadas para todas las clases que usa el código que no es de confianza, por lo que puede hacer cosas como deshabilitar el acceso a clases JDK individuales. Lo que hay que hacer es tener una lista blanca de clases JDK permitidas.

  4. Es posible que desee ejecutar el código que no es de confianza en una JVM separada.Si bien los pasos anteriores protegerían el código, hay una cosa molesta que el código aislado aún puede hacer: asignar tanta memoria como sea posible, lo que hace que la huella visible de la aplicación principal crezca.

JSR 121: Application Isolation API Specification fue diseñado para resolver esto, pero por desgracia, no tiene una aplicación aún.

Este es un tema bastante detallado, y sobre todo estoy escribiendo todo esto fuera de mi cabeza.

Pero de todos modos, algunos imperfecta, utilizar-en-su-propio-riesgo, probablemente con errores (pseudo) Código:

ClassLoader

class MyClassLoader extends ClassLoader { 
    @Override 
    public Class<?> loadClass(String name) throws ClassNotFoundException { 
    if (name is white-listed JDK class) return super.loadClass(name); 
    return findClass(name); 
    } 
    @Override 
    public Class findClass(String name) { 
    byte[] b = loadClassData(name); 
    return defineClass(name, b, 0, b.length); 
    } 
    private byte[] loadClassData(String name) { 
    // load the untrusted class data here 
    } 
} 

SecurityManager

class MySecurityManager extends SecurityManager { 
    private Object secret; 
    public MySecurityManager(Object pass) { secret = pass; } 
    private void disable(Object pass) { 
    if (pass == secret) secret = null; 
    } 
    // ... override checkXXX method(s) here. 
    // Always allow them to succeed when secret==null 
} 

Hilo

class MyIsolatedThread extends Thread { 
    private Object pass = new Object(); 
    private MyClassLoader loader = new MyClassLoader(); 
    private MySecurityManager sm = new MySecurityManager(pass); 
    public void run() { 
    SecurityManager old = System.getSecurityManager(); 
    System.setSecurityManager(sm); 
    runUntrustedCode(); 
    sm.disable(pass); 
    System.setSecurityManager(old); 
    } 
    private void runUntrustedCode() { 
    try { 
     // run the custom class's main method for example: 
     loader.loadClass("customclassname") 
     .getMethod("main", String[].class) 
     .invoke(null, new Object[]{...}); 
    } catch (Throwable t) {} 
    } 
} 
+4

Ese código podría necesitar algún trabajo. Realmente no puede protegerse contra la disponibilidad de JVM. Prepárate para matar el proceso (probablemente automáticamente). Codifique para obtener otros hilos, por ejemplo, el hilo del finalizador. 'Thread.stop' causará problemas en el código de la biblioteca Java. Del mismo modo, el código de la biblioteca Java requerirá permisos. Es mucho mejor permitir que 'SecurityManager' use' java.security.AccessController'. El cargador de clases probablemente también debería permitir el acceso a las propias clases del código de usuario. –

+0

@Tom: es posible implementar el administrador de seguridad de forma tal que ignore el código de la biblioteca Java. – instantsetsuna

+3

Dado que se trata de un tema tan complicado, ¿no hay soluciones existentes para manejar los "complementos" de Java de forma segura? –

18

Obviamente, tal esquema plantea todo tipo de problemas de seguridad. Java tiene un marco de seguridad riguroso, pero no es trivial. No se debe pasar por alto la posibilidad de atornillarlo y dejar que un usuario sin privilegios acceda a componentes vitales del sistema.

que la alerta a un lado, si usted está tomando la entrada del usuario en forma de código fuente, lo primero que hay que hacer es compilar a código de bytes de Java. AFIAK, esto no se puede hacer de forma nativa, por lo que deberá realizar una llamada al sistema a javac y compilar el código fuente en bytecode en el disco. Here's un tutorial que se puede utilizar como punto de partida para esto. Editar: como he aprendido en los comentarios, en realidad se puede compilar el código Java de la fuente nativa utilizando javax.tools.JavaCompiler

Una vez que tenga el código de bytes de JVM, puede cargarlo en la JVM utilizando una función ClassLoader'sdefineClass. Para establecer un contexto de seguridad para esta clase cargada, deberá especificar un ProtectionDomain. El constructor mínimo para un ProtectionDomain requiere un CodeSource y un PermissionCollection. El PermissionCollection es el objeto de uso principal para usted; puede usarlo para especificar los permisos exactos que tiene la clase cargada. Estos permisos deben ser aplicados en última instancia por el JVM AccessController.

Hay una gran cantidad de puntos posibles de error aquí, y que deben ser extremadamente cuidadosos para entender por completo todo antes de implementar cualquier cosa.

+2

La compilación de Java es bastante fácil usando la API javax.tools de JDK 6. –

10

La Java-Sandbox es una biblioteca para ejecutar código Java con un conjunto limitado de permisos. Se puede usar para permitir el acceso solo a un conjunto de clases y recursos de la lista blanca. No parece poder restringir el acceso a métodos individuales. Utiliza un sistema con un cargador de clases personalizado y un administrador de seguridad para lograr esto.

No lo he usado pero parece bien diseñado y razonablemente bien documentado.

@waqas ha dado una respuesta muy interesante explicando cómo es posible implementarlo usted mismo. Pero es mucho más seguro dejar tal código crítico y complejo de seguridad a los expertos.

Tenga en cuenta que el proyecto no se ha actualizado desde 2013 y los creadores lo describen como "experimental". Su página de inicio ha desaparecido pero permanece la entrada de Source Forge.

código Ejemplo adaptado de la página web del proyecto:

SandboxService sandboxService = SandboxServiceImpl.getInstance(); 

// Configure context 
SandboxContext context = new SandboxContext(); 
context.addClassForApplicationLoader(getClass().getName()); 
context.addClassPermission(AccessType.PERMIT, "java.lang.System"); 

// Whithout this line we get a SandboxException when touching System.out 
context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream"); 

String someValue = "Input value"; 

class TestEnvironment implements SandboxedEnvironment<String> { 
    @Override 
    public String execute() throws Exception { 
     // This is untrusted code 
     System.out.println(someValue); 
     return "Output value"; 
    } 
}; 

// Run code in sandbox. Pass arguments to generated constructor in TestEnvironment. 
SandboxedCallResult<String> result = sandboxService.runSandboxed(TestEnvironment.class, 
    context, this, someValue); 

System.out.println(result.get()); 
2

Aquí es una solución segura para los subprocesos para el problema:

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. 

} 

favor comentar!

CU

Arno

2

Para abordar el problema de la respuesta aceptada por el cual el encargo SecurityManager se aplicará a todas las discusiones en la JVM, en lugar de en una base por hilo, puede crear una costumbre SecurityManager que puede ser habilitado/inhabilitado para roscas específicas de la siguiente manera:

import java.security.Permission; 

public class SelectiveSecurityManager extends SecurityManager { 

    private static final ToggleSecurityManagerPermission TOGGLE_PERMISSION = new ToggleSecurityManagerPermission(); 

    ThreadLocal<Boolean> enabledFlag = null; 

    public SelectiveSecurityManager(final boolean enabledByDefault) { 

    enabledFlag = new ThreadLocal<Boolean>() { 

     @Override 
     protected Boolean initialValue() { 
     return enabledByDefault; 
     } 

     @Override 
     public void set(Boolean value) { 
     SecurityManager securityManager = System.getSecurityManager(); 
     if (securityManager != null) { 
      securityManager.checkPermission(TOGGLE_PERMISSION); 
     } 
     super.set(value); 
     } 
    }; 
    } 

    @Override 
    public void checkPermission(Permission permission) { 
    if (shouldCheck(permission)) { 
     super.checkPermission(permission); 
    } 
    } 

    @Override 
    public void checkPermission(Permission permission, Object context) { 
    if (shouldCheck(permission)) { 
     super.checkPermission(permission, context); 
    } 
    } 

    private boolean shouldCheck(Permission permission) { 
    return isEnabled() || permission instanceof ToggleSecurityManagerPermission; 
    } 

    public void enable() { 
    enabledFlag.set(true); 
    } 

    public void disable() { 
    enabledFlag.set(false); 
    } 

    public boolean isEnabled() { 
    return enabledFlag.get(); 
    } 

} 

ToggleSecurirtyManagerPermission es sólo una implementación sencilla de java.security.Permission para asegurar que sólo autoriza el código puede habilitar/deshabilitar el administrador de seguridad. Se ve así:

import java.security.Permission; 

public class ToggleSecurityManagerPermission extends Permission { 

    private static final long serialVersionUID = 4812713037565136922L; 
    private static final String NAME = "ToggleSecurityManagerPermission"; 

    public ToggleSecurityManagerPermission() { 
    super(NAME); 
    } 

    @Override 
    public boolean implies(Permission permission) { 
    return this.equals(permission); 
    } 

    @Override 
    public boolean equals(Object obj) { 
    if (obj instanceof ToggleSecurityManagerPermission) { 
     return true; 
    } 
    return false; 
    } 

    @Override 
    public int hashCode() { 
    return NAME.hashCode(); 
    } 

    @Override 
    public String getActions() { 
    return ""; 
    } 

} 
+2

Citando sus (propias) fuentes: http://alphaloop.blogspot.com/2014/08/a-per-thread-java-security-manager.html y https://github.com/alphaloop/selective-security- gerente . – ziesemer

4

Bueno, es muy tarde para dar alguna sugerencia o soluciones, pero todavía se enfrenta el mismo tipo de tema, tipo de orientación más investigación. Básicamente, estaba tratando de proporcionar una provisión y evaluaciones automáticas para las asignaciones de programación para el curso de Java en las plataformas de e-learning.

  1. una forma podría ser, crear una máquina virtual separada (no JVM), pero las máquinas virtuales reales con la configuración mínima posible sistema operativo para cada uno de los estudiantes.
  2. Instale JRE para Java o bibliotecas según sus lenguajes de programación, lo que quiera que los estudiantes compilen y ejecuten en estas máquinas.

Sé que esto suena bastante complejo y la gran cantidad de tareas, pero Oracle Virtual Box ya proporciona API de Java para crear o clonar máquinas virtuales de forma dinámica. https://www.virtualbox.org/sdkref/index.html (Tenga en cuenta, incluso VMware también proporciona API para hacer lo mismo)

Y para el tamaño mínimo y distribución de Linux de configuración se puede hacer referencia a este de aquí http://www.slitaz.org/en/,

Así que ahora si los estudiantes estropea o trata de hacer puede ser con memoria o sistema de archivos o red, socket, máximo puede dañar su propia máquina virtual.

También internamente en estas máquinas virtuales puede proporcionar seguridad adicional como Sandbox (administrador de seguridad) para Java o crear cuentas específicas para el usuario en Linux y, por lo tanto, restringir el acceso.

Espero que esto ayude !!

Cuestiones relacionadas