Песочница против вредоносного кода в приложении Java - PullRequest
86 голосов
/ 02 февраля 2009

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

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

Ответы [ 7 ]

107 голосов
/ 02 февраля 2009
  1. Запустите ненадежный код в своем собственном потоке. Это, например, предотвращает проблемы с бесконечными циклами и тому подобное, и облегчает будущие шаги. Дайте основному потоку дождаться его завершения, а если потребуется слишком много времени, убейте его с помощью Thread.stop. Thread.stop устарел, но поскольку у ненадежного кода не должно быть доступа ни к каким ресурсам, было бы безопасно уничтожить его.

  2. Установите SecurityManager в этой теме. Создайте подкласс SecurityManager, который переопределяет checkPermission (Perm perm) , чтобы просто выдавать SecurityException для всех разрешений, кроме нескольких избранных. Вот список методов и разрешений, которые им требуются: Разрешения в Java TM 6 SDK .

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

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

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

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

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

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
}

Тема

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) {}
  }
}
18 голосов
/ 02 февраля 2009

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

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

Получив байт-код JVM, вы можете загрузить его в JVM с помощью функции ClassLoader defineClass . Чтобы установить контекст безопасности для этого загруженного класса, вам нужно указать ProtectionDomain . Минимальный конструктор для ProtectionDomain требует как CodeSource, так и PermissionCollection . Здесь PermissionCollection является объектом первичного использования. Вы можете использовать его, чтобы указать точные разрешения, которые имеет загруженный класс. В конечном итоге эти разрешения должны обеспечиваться AccessController JVM .

Здесь много возможных точек ошибки, и вы должны быть предельно осторожны, чтобы полностью понять все, прежде чем что-либо реализовывать.

10 голосов
/ 18 ноября 2013

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

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

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

Обратите внимание, что проект не обновлялся с 2013 года, и создатели описывают его как «экспериментальный». Его домашняя страница исчезла, но запись Source 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());
4 голосов
/ 13 марта 2015

Что ж, уже очень поздно давать какие-либо предложения или решения, но все же я столкнулся с подобной проблемой, более ориентированной на исследования. По сути, я пытался обеспечить предоставление и автоматическую оценку заданий программирования для курса 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 (менеджер безопасности) для Java или создание учетных записей для конкретных пользователей в Linux и, таким образом, ограничение доступа.

Надеюсь, это поможет !!

2 голосов
/ 26 августа 2014

Чтобы решить проблему в принятом ответе, согласно которому пользовательский 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 "";
  }

}
2 голосов
/ 15 июня 2014

Вот многопоточное решение проблемы:

https://svn.code.sf.net/p/loggifier/code/trunk/de.unkrig.commons.lang/src/de/unkrig/commons/lang/security/Sandbox.java

<code>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();
 * 
* / публичный финал песочница класса { личная песочница () {} приватная статическая финальная карта , AccessControlContext> CHECKED_CLASSES = Collections.synchronizedMap (новый WeakHashMap , AccessControlContext> ()); частная статическая конечная карта CHECKED_CLASS_NAMES = Collections.synchronizedMap (new HashMap ()); приватная статическая финальная карта CHECKED_CLASS_LOADERS = Collections.synchronizedMap (new WeakHashMap ()); static { // Установить наш собственный менеджер безопасности. if (System.getSecurityManager ()! = null) { бросить новый ExceptionInInitializerError («Менеджер безопасности уже настроен»); } System.setSecurityManager (new SecurityManager () { @ Overide public void checkPermission (@Nullable Permission perm) { утверждать Пермь! = ноль; for (Class <?> clasS: this.getClassContext ()) { // Проверяем, был ли установлен ACC для класса. { AccessControlContext acc = Sandbox.CHECKED_CLASSES.get (clasS); if (acc! = null) acc.checkPermission (perm); } // Проверяем, был ли установлен ACC для имени класса. { AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get (clasS.getName ()); if (acc! = null) acc.checkPermission (perm); } // Проверяем, был ли установлен ACC для загрузчика классов. { AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get (clasS.getClassLoader ()); if (acc! = null) acc.checkPermission (perm); } } } }); } // -------------------------- / ** * Все будущие действия, которые выполняются с помощью {@code clasS}, будут проверены на соответствие с {@code * accessControlContext}. * * @throws SecurityException Разрешения уже ограничены для {@code clasS} * / публичная статическая пустота ограничение (класс <?> clasS, AccessControlContext accessControlContext) { if (Sandbox.CHECKED_CLASSES.containsKey (clasS)) { выбросить новое SecurityException («Попытка изменить контекст управления доступом для« + clasS + »); } Sandbox.CHECKED_CLASSES.put (clasS, accessControlContext); } / ** * Все будущие действия, которые выполняются с помощью {@code clasS}, будут проверены на соответствие с {@code * protectionDomain}. * * @throws SecurityException Разрешения уже ограничены для {@code clasS} * / публичная статическая пустота ограничение (класс <?> clasS, ProtectionDomain protectionDomain) { Sandbox.confine ( учебный класс, новый AccessControlContext (новый ProtectionDomain [] {protectionDomain}) ); } / ** * Все будущие действия, которые выполняются с помощью {@code clasS}, будут проверены на соответствие с {@code * разрешения}. * * @throws SecurityException Разрешения уже ограничены для {@code clasS} * / публичная статическая пустота ограничение (класс <?> clasS, права доступа) { Sandbox.confine (clasS, новый ProtectionDomain (ноль, разрешения)); } // Код для CHECKED_CLASS_NAMES и CHECKED_CLASS_LOADERS здесь опущен. }

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

CU

Arno

0 голосов
/ 02 февраля 2009

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

...