Пісочниця проти шкідливого коду в програмі Java


91

У середовищі симуляційного сервера, де користувачам дозволено подавати власний код для запуску сервером, очевидно, було б вигідно, щоб будь-який представлений користувачем код запускався в бічній пісочниці, на відміну від аплетів, що знаходяться в браузері. Я хотів мати можливість використовувати саму JVM, а не додавати інший рівень VM для ізоляції цих представлених компонентів.

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

Відповіді:


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

4
Цей код може потребувати певної роботи. Ви насправді не можете захиститися від наявності JVM. Будьте готові вбити процес (можливо, автоматично). Код потрапляє до інших потоків - наприклад, до фіналізатора. Thread.stopспричинить проблеми в коді бібліотеки Java. Подібним чином для коду бібліотеки Java потрібні дозволи. Набагато краще дозволити SecurityManagerкористуватися java.security.AccessController. Завантажувач класів, ймовірно, також повинен надавати доступ до власних класів коду користувача.
Том Хоутін - таклін

3
Враховуючи, що це така складна тема, чи не існують рішення для безпечної роботи з «плагінами» Java?
Нік Спейк,

9
Проблема цього підходу полягає в тому, що коли ви встановлюєте SecurityManager на System, він впливає не лише на запущений потік, але й на інший потік!
Gelin Luo

2
Вибачте, але thread.stop () можна перехопити за допомогою функції кидання. Ви можете while (thread.isAlive) Thread.stop (), але тоді я можу рекурсивно викликати функцію, яка вловлює виняток. Перевірено на моєму ПК, рекурсивна функція перемагає стоп (). Тепер у вас сміттєва нитка, крадіжка процесора та ресурсів
Лесто

8
Окрім того, що System.setSecurityManager(…)вплине на всю JVM, а не лише на потік, що викликає цей метод, ідея прийняття рішень щодо безпеки на основі потоку була відмовлена ​​від переходу Java з 1.0 на 1.1. На цей час було визнано, що ненадійний код може викликати надійний код і навпаки, незалежно від того, який потік виконує код. Жоден розробник не повинен повторювати помилку.
Холгер

18

Очевидно, що така схема породжує всілякі проблеми безпеки. Java має сувору систему безпеки, але вона не є тривіальною. Не можна нехтувати можливістю зіпсувати це і надати непривілейованому користувачеві доступ до важливих системних компонентів.

Це попередження осторонь, якщо ви берете введення користувача у вигляді вихідного коду, перше, що вам потрібно зробити, - це скомпілювати його в байт-код Java. AFIAK, цього не можна зробити спочатку, тому вам потрібно буде здійснити системний виклик javac та скомпілювати вихідний код в байт-код на диску. Ось підручник, який може бути використаний як відправна точка для цього. Редагувати : як я дізнався в коментарях, ви насправді можете скомпілювати код Java із вихідного коду за допомогою javax.tools.JavaCompiler

Отримавши байт-код JVM, ви можете завантажити його в JVM, використовуючи функцію defineClass ClassLoader . Щоб встановити контекст безпеки для цього завантаженого класу, вам потрібно буде вказати ProtectionDomain . Мінімальний конструктор для ProtectionDomain вимагає як CodeSource, так і PermissionCollection . PermissionCollection є об’єктом первинного використання для вас тут - ви можете використовувати його, щоб вказати точні дозволи, які має завантажений клас. Ці дозволи повинні в кінцевому підсумку застосовуватися AccessController JVM .

Тут є багато можливих моментів помилок, і ви повинні бути надзвичайно обережними, щоб повністю зрозуміти все, перш ніж щось реалізовувати.


2
Компіляція Java досить проста за допомогою API javax.tools JDK 6.
Алан Крюгер

10

Java-Sandbox бібліотека для виконання 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

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

}

3
Посилаючись на свої (власні) джерела: alphaloop.blogspot.com/2014/08/… та github.com/alphaloop/selective-security-manager .
ziesemer

Дуже розумне використання ThreadLocal для створення системного охоплення SecurityManagers ефективно з обмеженими потоками (чого бажає більшість користувачів). Також розгляньте можливість використання InheritableThreadLocal для автоматичної передачі забороненого властивості потокам, породженим ненадійним кодом.
Нік

4

Ну, дуже пізно давати будь-які пропозиції чи рішення, але все-таки я стикався з подібними проблемами, якимись більш орієнтованими на дослідження. В основному я намагався надати положення та автоматичне оцінювання завдань програмування для курсу 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 і, таким чином, обмеження доступу.

Сподіваюся, це допомагає !!


3

Ось безпечне для вирішення проблеми рішення:

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.

}

Будь ласка, коментуйте!

КС

Арно


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.