Песочница против вредоносного кода в приложении Java

в среде сервера моделирования, где пользователям разрешено отправлять свой собственный код для запуска сервером, было бы явно выгодно, чтобы любой пользовательский код запускался в боковой песочнице, в отличие от апплетов в браузере. Я хотел иметь возможность использовать сам JVM, а не добавлять другой слой VM для изоляции этих представленных компонентов.

кажется, что такое ограничение возможно с использованием существующей модели песочницы Java, но есть ли динамический способ включить это только для пользовательских частей запущенного приложения?

7 ответов


  1. запустите ненадежный код в своем собственном потоке. Это, например, предотвращает проблемы с бесконечными циклами и тому подобное, и облегчает будущие шаги. Попросите основной поток дождаться завершения потока, и если это займет слишком много времени, убейте его потоком.остановка. Нитка.stop устарел, но поскольку ненадежный код не должен иметь доступа к каким-либо ресурсам, было бы безопасно его убить.

  2. поставил SecurityManager по этой теме. Создать подкласс SecurityManager, который переопределяет checkPermission (разрешение Пермь) просто бросить SecurityException для всех разрешений, кроме нескольких избранных. Существует список методов и разрешений, которые они требуют здесь:разрешения в JavaTM 6 SDK.

  3. использовать пользовательский загрузчик классов для загрузки ненадежного кода. Ваш загрузчик классов будет вызываться для всех классов, которые использует ненадежный код, поэтому вы может делать такие вещи, как отключить доступ к отдельным классам JDK. Дело в том, чтобы иметь белый список разрешенных классов JDK.

  4. возможно, вы захотите запустить ненадежный код в отдельной JVM. Хотя предыдущие шаги сделали бы код безопасным, есть одна раздражающая вещь, которую изолированный код все еще может сделать: выделить столько памяти, сколько он может, что заставляет видимый след основного приложения расти.

JSR 121: применение Спецификация API изоляции был разработан для решения этой проблемы, но, к сожалению, у него еще нет реализации.

это довольно подробная тема,и я в основном пишу все это с головы.

но в любом случае, некоторые несовершенные, использовать на свой страх и риск, вероятно, багги (псевдо) код:

загрузчика

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
}

нить

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

очевидно, что такая схема вызывает всякие проблемы безопасности. Java имеет строгую структуру безопасности, но это не тривиально. Нельзя упускать из виду возможность завинчивания и предоставления непривилегированному Пользователю доступа к жизненно важным компонентам системы.

Это предупреждение в сторону, если вы принимаете пользовательский ввод в виде исходного кода, первое, что вам нужно сделать, это скомпилировать его в байт-код Java. AFIAK, это нельзя сделать изначально, поэтому вам нужно будет сделать системный вызов javac, и скомпилируйте исходный код для байт-кода на диске. здесь учебник, который может быть использован в качестве отправной точки для этого. редактировать: как я узнал в комментариях, вы действительно можете скомпилировать Java-код из исходного кода, используя javax.инструменты.JavaCompiler

Как только у вас есть байт-код JVM, вы можете загрузить его в JVM, используя загрузчика классов это defineClass


на Java-Sandbox - библиотека для выполнения кода Java с ограниченным набором разрешений. Он может использоваться для доступа только к набору классов и ресурсов с белым списком. Не похоже. возможность ограничения доступа к отдельным методам. Он использует систему с пользовательским загрузчиком классов и security manager для достижения этого.

Я не использовал его, но он выглядит хорошо спроектированным и достаточно хорошо документированным.

@waqas дал очень интересный ответ, объясняющий, как это можно реализовать самостоятельно. Но гораздо безопаснее оставить такой критический и сложный код безопасности экспертам.

обратите внимание, что проект не обновлялся с 2013 года, и создатели описывают его как"экспериментальный". Его домашняя страница исчезла, но запись источника Forge остается.

пример кода, адаптированного с веб-сайта проекта:

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

ну, очень поздно давать какие-либо предложения или решения, но все же я столкнулся с подобной проблемой, более ориентированной на исследования. В основном я пытался обеспечить предоставление и автоматическую оценку заданий программирования для Java-курса на платформах электронного обучения.

  1. один из способов может быть, создать отдельные виртуальные машины (не JVM), но фактические виртуальные машины с минимальной конфигурацией возможной ОС для каждого из студентов.
  2. установить JRE для Java или библиотеки в соответствии с вашими языками программирования, в зависимости от того, что вы хотите, чтобы студенты компилировали и выполняли на этих машинах.

Я знаю, что это звучит довольно сложно и много задач, но Oracle Virtual Box уже предоставляет Java API для создания или клонирования виртуальных машин динамически. https://www.virtualbox.org/sdkref/index.html (заметь, даже VMware также предоставляет API для этого же)

и для минимального размера и конфигурации дистрибутива Linux вы можете обратиться к этому здесь http://www.slitaz.org/en/,

Так что теперь, если студенты испортит или пытается сделать это, может быть с памятью или файловой системы или сети, сокет, максимум он может повредить свою собственную виртуальную машину.

также внутри этих виртуальных машин вы можете обеспечить дополнительную безопасность, такую как Sandbox (security manager ) для Java или создание учетных записей пользователей в Linux и, таким образом, ограничение доступа.

надеюсь, что это помогает !!


вот потокобезопасное решение проблемы:

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.

}

прокомментируйте, пожалуйста!

CU

Арно


для решения проблемы в принятом ответе, в котором пользовательский SecurityManager будет применяться ко всем потокам в JVM, а не на основе каждого потока, вы можете создать пользовательский SecurityManager Это можно включить / отключить для определенных потоков следующим образом:

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 это просто простая реализация java.security.Permission чтобы гарантировать, что только авторизованные кода включить/отключить менеджер безопасности. Выглядит это так:

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

}

вам, вероятно, придется использовать пользовательский SecurityManger и/или AccessController. Подробнее см. В разделе Архитектура Безопасности Java и другая документация по безопасности от Солнца.