Безопасность Java: плагины песочницы, загружаемые через URLClassLoader - PullRequest
33 голосов
/ 16 октября 2010

Сводка вопроса: Как изменить приведенный ниже код так, чтобы ненадежный динамически загружаемый код выполнялся в изолированной программной среде безопасности, а остальная часть приложения оставалась неограниченной?Почему URLClassLoader не обрабатывает его так, как он говорит?

РЕДАКТИРОВАТЬ: Обновлено, чтобы отвечать Ани Б.

РЕДАКТИРОВАТЬ 2: Добавлен обновленный PluginSecurityManager.

Мое приложение имеет механизм плагинов, в котором третья сторона может предоставить JAR, содержащий класс, который реализует определенный интерфейс.Используя URLClassLoader, я могу загрузить этот класс и создать его экземпляр, никаких проблем.Поскольку код потенциально ненадежен, я должен предотвратить его неправильное поведение.Например, я запускаю плагин-код в отдельном потоке, чтобы я мог его убить, если он заходит в бесконечный цикл или просто занимает слишком много времени.Но попытка установить для них изолированную программную среду безопасности, чтобы они не могли делать такие вещи, как создание сетевых подключений или доступ к файлам на жестком диске, заставляет меня решительно настроиться.Мои усилия всегда приводят либо к тому, что не влияют на плагин (он имеет те же права доступа, что и приложение), либо также ограничивают приложение.Я хочу, чтобы основной код приложения мог выполнять практически все, что ему нужно, но код подключаемого модуля должен быть заблокирован.

Документация и онлайн-ресурсы по этой теме сложны, запутаны и противоречивы.Я читал в разных местах (например, этот вопрос ), что мне нужно предоставить собственный SecurityManager, но когда я пытаюсь это сделать, у меня возникают проблемы, потому что JVM лениво загружает классы в JAR.Так что я могу просто создать его экземпляр, но если я вызову метод для загруженного объекта, который создает экземпляр другого класса из того же JAR, он взорвется, потому что ему будет отказано в праве на чтение из JAR.

Теоретически ямог бы поставить проверку FilePermission в моем SecurityManager, чтобы увидеть, пытается ли он загрузить его из собственного JAR-файла.Это нормально, но документация URLClassLoader гласит: «Загруженные классы по умолчанию предоставляют разрешение только на доступ к URL-адресам, указанным при создании URLClassLoader».Так зачем мне вообще нужен собственный SecurityManager?Разве URLClassLoader не должен просто обрабатывать это?Почему нет?

Вот упрощенный пример, который воспроизводит проблему:

Основное приложение (доверенное)

PluginTest.java

package test.app;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

import test.api.Plugin;

public class PluginTest {
    public static void pluginTest(String pathToJar) {
        try {
            File file = new File(pathToJar);
            URL url = file.toURI().toURL();
            URLClassLoader cl = new URLClassLoader(new java.net.URL[] { url });
            Class<?> clazz = cl.loadClass("test.plugin.MyPlugin");
            final Plugin plugin = (Plugin) clazz.newInstance();
            PluginThread thread = new PluginThread(new Runnable() {
                @Override
                public void run() {
                    plugin.go();
                }
            });
            thread.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Plugin.java

package test.api;

public interface Plugin {
    public void go();
}

PluginSecurityManager.java

package test.app;

public class PluginSecurityManager extends SecurityManager {
    private boolean _sandboxed;

    @Override
    public void checkPermission(Permission perm) {
        check(perm);
    } 

    @Override
    public void checkPermission(Permission perm, Object context) {
        check(perm);
    }

    private void check(Permission perm) {
        if (!_sandboxed) {
            return;
        }

        // I *could* check FilePermission here, but why doesn't
        // URLClassLoader handle it like it says it does?

        throw new SecurityException("Permission denied");
    }

    void enableSandbox() {
    _sandboxed = true;
    }

    void disableSandbox() {
        _sandboxed = false;
    }
}

PluginThread.java

package test.app;

class PluginThread extends Thread {
    PluginThread(Runnable target) {
        super(target);
    }

    @Override
    public void run() {
        SecurityManager old = System.getSecurityManager();
        PluginSecurityManager psm = new PluginSecurityManager();
        System.setSecurityManager(psm);
        psm.enableSandbox();
        super.run();
        psm.disableSandbox();
        System.setSecurityManager(old);
    }
}

Плагин JAR (ненадежный)

MyPlugin.java

package test.plugin;

public MyPlugin implements Plugin {
    @Override
    public void go() {
        new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
        doSomethingDangerous(); // permitted without a SecurityManager
    }

    private void doSomethingDangerous() {
        // use your imagination
    }
}

ОБНОВЛЕНИЕ: Я изменил его так, что перед самым запуском кода плагина он уведомляет PluginSecurityManager, чтобы он знал, с каким источником класса он работает,Тогда он разрешит доступ к файлам только для файлов с исходным путем этого класса.Это также имеет то преимущество, что я могу просто установить диспетчер безопасности один раз в начале своего приложения и просто обновить его, когда я ввожу и оставляю код плагина.

Это в значительной степени решает проблему, но нене могу ответить на другой мой вопрос: почему URLClassLoader не обрабатывает это для меня так, как говорит?Я оставлю этот вопрос открытым на некоторое время, чтобы узнать, есть ли у кого-нибудь ответ на этот вопрос.Если так, то этот человек получит принятый ответ.В противном случае я передам его Ани Б., исходя из предположения, что документация URLClassLoader является ложной и что его совет по созданию пользовательского SecurityManager верен.

PluginThread должен будет установить свойство classSource для PluginSecurityManager,который является путем к файлам класса.PluginSecurityManager теперь выглядит примерно так:

package test.app;

public class PluginSecurityManager extends SecurityManager {
    private String _classSource;

    @Override
    public void checkPermission(Permission perm) {
        check(perm);
    } 

    @Override
    public void checkPermission(Permission perm, Object context) {
        check(perm);
    }

    private void check(Permission perm) {
        if (_classSource == null) {
            // Not running plugin code
            return;
        }

        if (perm instanceof FilePermission) {
            // Is the request inside the class source?
            String path = perm.getName();
            boolean inClassSource = path.startsWith(_classSource);

            // Is the request for read-only access?
            boolean readOnly = "read".equals(perm.getActions());

            if (inClassSource && readOnly) {
                return;
            }
        }

        throw new SecurityException("Permission denied: " + perm);
    }

    void setClassSource(String classSource) {
    _classSource = classSource;
    }
}

Ответы [ 3 ]

7 голосов
/ 19 октября 2010

Из документов:
The AccessControlContext of the thread that created the instance of URLClassLoader will be used when subsequently loading classes and resources.

The classes that are loaded are by default granted permission only to access the URLs specified when the URLClassLoader was created.

URLClassLoader делает именно то, что говорит, AccessControlContext - это то, на что вам нужно смотреть По сути, у потока, на который ссылается AccessControlContext, нет прав на выполнение того, что вы думаете.

7 голосов
/ 24 августа 2011

Я использую следующий подход при запуске скрипта Groovy в приложении. Я явно хочу, чтобы скрипт не запускал (намеренно или непреднамеренно) System.exit

Я устанавливаю java SecurityManager обычным способом:

-Djava.security.manager -Djava.security.policy=<policy file>

В <policy file> я даю своему приложению все разрешения (я полностью доверяю своему приложению), т. Е.

grant {
    permission java.security.AllPermission;
};

Я ограничиваю возможности в той части, где выполняется скрипт Groovy:

list = AccessController.doPrivileged(new PrivilegedExceptionAction<List<Stuff>> () {
    public List<Stuff> run() throws Exception {
        return groovyToExecute.someFunction();
    }
}, allowedPermissionsAcc);

allowedPermissionsAcc не меняется, и поэтому я создаю их в статическом блоке

private static final AccessControlContext allowedPermissionsAcc; 
static {    // initialization of the allowed permissions
    PermissionCollection allowedPermissions = new Permissions();
    allowedPermissions.add(new RuntimePermission("accessDeclaredMembers"));
    // ... <many more permissions here> ...

    allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] {
        new ProtectionDomain(null, allowedPermissions)});
}

Теперь самое сложное - найти правильные разрешения.

Если вы хотите разрешить доступ к определенным библиотекам, вы быстро поймете, что они не были написаны с учетом Security Manager, и не справились с ними очень элегантно, и выяснить, какие разрешения им нужны, может быть довольно сложно. Вы столкнетесь с дополнительными проблемами, если захотите запустить UnitTests через плагин Maven Surefire или запустить на разных платформах, таких как Linux / Windows, поскольку поведение может различаться :-(. Но эти проблемы - другая тема

5 голосов
/ 18 октября 2010

Реализация SecurityManager, вероятно, лучший путь. Вы должны переопределить checkPermission. Этот метод будет смотреть на переданный ему объект Permission и определять, опасно ли определенное действие. Таким образом, вы можете разрешить некоторые разрешения и запретить другие разрешения.

Можете ли вы описать пользовательский SecurityManager, который вы использовали?

...