java - site - comment débloquer une application bloquée par les paramètres de sécurité android



Sandbox contre le code malveillant dans une application Java (5)

Dans un environnement de serveur de simulation où les utilisateurs sont autorisés à soumettre leur propre code pour être exécuté par le serveur, il serait clairement avantageux que tout code soumis par l'utilisateur soit exécuté dans un sandbox, contrairement à Applets dans un navigateur. Je voulais être en mesure de tirer parti de la JVM elle-même, plutôt que d'ajouter une autre couche VM pour isoler ces composants soumis.

Ce type de limitation semble être possible en utilisant le modèle de sandbox Java existant, mais existe-t-il un moyen dynamique de l'activer uniquement pour les parties soumises par l'utilisateur d'une application en cours d'exécution?

https://ffff65535.com


  1. Exécutez le code non approuvé dans son propre thread. Cela évite par exemple des problèmes avec des boucles infinies et autres, et facilite les étapes futures. Demandez au thread principal d'attendre la fin du thread, et si cela prend trop de temps, tuez-le avec Thread.stop. Thread.stop est obsolète, mais comme le code non approuvé ne devrait avoir accès à aucune ressource, il serait prudent de le tuer.

  2. Définissez un SecurityManager sur ce thread. Créez une sous-classe de SecurityManager qui remplace checkPermission (Permission perm) pour simplement lancer une exception SecurityException pour toutes les permissions sauf quelques select. Voici une liste de méthodes et d'autorisations requises: Autorisations dans Java TM 6 SDK .

  3. Utilisez un ClassLoader personnalisé pour charger le code non approuvé. Votre chargeur de classe est appelé pour toutes les classes utilisées par le code non fiable, ce qui vous permet de désactiver l'accès à des classes JDK individuelles. La chose à faire est d'avoir une liste blanche des classes JDK autorisées.

  4. Vous souhaiterez peut-être exécuter le code non approuvé dans une machine virtuelle Java distincte. Alors que les étapes précédentes rendraient le code sûr, il y a une chose gênante que le code isolé peut encore faire: allouer autant de mémoire que possible, ce qui provoque une augmentation de l'empreinte visible de l'application principale.

JSR 121: La spécification de l'API d'isolation des applications a été conçue pour résoudre ce problème, mais malheureusement, elle n'a pas encore d'implémentation.

C'est un sujet assez détaillé, et je suis surtout en train de l'écrire du haut de ma tête.

Mais de toute façon, un code imparfait, use-at-your-own-risk, probablement buggé (pseudo):

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
  }
}

Responsable de la sécurité

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
}

Fil

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) {}
  }
}

De toute évidence, un tel système soulève toutes sortes de problèmes de sécurité. Java a un cadre de sécurité rigoureux, mais ce n'est pas trivial. La possibilité de le visser et de laisser un utilisateur non privilégié accéder aux composants essentiels du système ne doit pas être négligée.

Cet avertissement de côté, si vous prenez l'entrée de l'utilisateur sous la forme de code source, la première chose que vous devez faire est de le compiler au bytecode Java. AFIAK, ceci ne peut pas être fait nativement, donc vous devrez faire un appel système à javac, et compiler le code source en bytecode sur le disque. Here's un tutoriel qui peut être utilisé comme point de départ pour cela. Edit : comme je l'ai appris dans les commentaires, vous pouvez réellement compiler le code Java de la source nativement en utilisant javax.tools.JavaCompiler

Une fois que vous avez le bytecode JVM, vous pouvez le charger dans la JVM en utilisant ClassLoader's fonction ClassLoader's defineClass . Pour définir un contexte de sécurité pour cette classe chargée, vous devez spécifier un objet ProtectionDomain . Le constructeur minimal d'un objet ProtectionDomain requiert à la fois un CodeSource et un PermissionCollection . Le PermissionCollection est l'objet d'utilisation primaire pour vous ici - vous pouvez l'utiliser pour spécifier les autorisations exactes de la classe chargée. Ces autorisations devraient être finalement appliquées par AccessController la JVM.

Il y a beaucoup de points d'erreur possibles ici, et vous devriez être extrêmement prudent pour comprendre complètement tout avant d'implémenter quoi que ce soit.


Pour résoudre le problème dans la réponse acceptée, le SecurityManager personnalisé s'appliquera à tous les threads de la JVM, plutôt que sur une base par thread, vous pouvez créer un SecurityManager personnalisé qui peut être activé / désactivé pour des threads spécifiques comme suit:

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 est juste une implémentation simple de java.security.Permission pour s'assurer que seul le code autorisé peut activer / désactiver le gestionnaire de sécurité. Cela ressemble à ceci:

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 "";
  }

}

Voici une solution thread-safe pour le problème:

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.

}

Commentez s'il vous plaît!

CU

Arno


Java-Sandbox est une bibliothèque permettant d'exécuter du code Java avec un ensemble limité d'autorisations. Il peut être utilisé pour autoriser l'accès à uniquement un ensemble de classes et de ressources répertoriées en blanc. Il ne semble pas pouvoir restreindre l'accès à des méthodes individuelles. Il utilise un système avec un chargeur de classe personnalisé et un gestionnaire de sécurité pour y parvenir.

Je ne l'ai pas utilisé mais il a l'air bien conçu et raisonnablement bien documenté.

@waqas a donné une réponse très intéressante expliquant comment cela est possible de vous implémenter. Mais il est beaucoup plus sûr de laisser ce code critique et complexe à des experts.

Notez cependant que le projet n'a pas été mis à jour depuis 2013 et que les créateurs le décrivent comme "expérimental". Sa page d'accueil a disparu mais l'entrée Source Forge reste.

Exemple de code adapté du site web du projet:

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




sandbox