Рубрики
Без рубрики

Удаленное выполнение кода с помощью XStream

Узнайте, как предотвратить атаку на удаленное выполнение кода в приложении, использующем XStream для чтения XML

Автор оригинала: Johnathan Gilday.

1. Обзор

В этом уроке мы рассмотрим атаку удаленного выполнения кода на библиотеку сериализации XML XStream. Этот эксплойт относится к категории атак ненадежная десериализация .

Мы узнаем, когда XStream уязвим для этой атаки, как работает атака и как предотвратить такие атаки.

2. Основы XStream

Прежде чем описать атаку, давайте рассмотрим некоторые основы XStream. XStream-это библиотека сериализации XML, которая переводит между типами Java и XML. Рассмотрим простой Человек класс:

public class Person {
    private String first;
    private String last;

    // standard getters and setters
}

Давайте посмотрим, как XStream может записать некоторый экземпляр Person в XML:

XStream xstream = new XStream();
String xml = xstream.toXML(person);

Аналогично, XStream может считывать XML в экземпляр Person:

XStream xstream = new XStream();
xstream.alias("person", Person.class);
String xml = "JohnSmith";
Person person = (Person) xstream.fromXML(xml);

В обоих случаях XStream использует Java reflection для перевода типа Person в XML и из XML. Атака происходит во время чтения XML. При чтении XML XStream создает экземпляры классов Java с помощью отражения.

Экземпляры классов XStream определяются именами XML-элементов, которые он анализирует.

Поскольку мы настроили XStream так, чтобы он знал о типе Person , XStream создает новый экземпляр Person при анализе XML-элементов с именем “person”.

В дополнение к определяемым пользователем типам , таким как Person , XStream распознает основные типы Java из коробки. Например, XStream может считывать Map из XML:

String xml = "" 
    + "" 
    + "  " 
    + "    foo" 
    + "    10" 
    + "  " 
    + "";
XStream xStream = new XStream();
Map map = (Map) xStream.fromXML(xml);

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

3. Как работает атака

Атаки на удаленное выполнение кода происходят, когда злоумышленники предоставляют входные данные, которые в конечном итоге интерпретируются как код. В этом случае злоумышленники используют стратегию десериализации Xstream, предоставляя код атаки в виде XML.

При правильном составе классов XStream в конечном итоге запускает код атаки через отражение Java.

Давайте построим пример атаки.

3.1. Включить код атаки в ProcessBuilder

Наша атака направлена на запуск нового процесса настольного калькулятора. В macOS это “/Applications/Calculator.app”. В Windows это “calc.exe”. Для этого мы заставим XStream запустить новый процесс с помощью ProcessBuilder. Вызовите Java-код для запуска нового процесса :

new ProcessBuilder().command("executable-name-here").start();

При чтении XML XStream вызывает только конструкторы и задает поля. Таким образом, у злоумышленника нет простого способа вызвать метод ProcessBuilder.start () .

Однако умные злоумышленники могут использовать правильную композицию классов, чтобы в конечном итоге выполнить метод ProcessBuilder ‘s start () .

Исследователь безопасности Динис Круз показывает нам в своем блоге как они используют интерфейс Comparable для вызова кода атаки в конструкторе копирования отсортированной коллекции TreeSet. Мы подытожим этот подход здесь.

3.2. Создание сопоставимого динамического Прокси-сервера

Напомним, что злоумышленнику необходимо создать ProcessBuilder и вызвать его метод start () . Для этого мы создадим экземпляр Comparable , чей метод compare вызывает метод ProcessBuilder ‘s start () .

К счастью, Динамические прокси Java позвольте нам создать экземпляр Сравнимый динамично .

Кроме того, класс Java Обработчик событий предоставляет злоумышленнику настраиваемый InvocationHandler Реализация. Злоумышленник настраивает обработчик событий для вызова метода ProcessBuilder ‘s start () .

Собрав эти компоненты вместе, мы получим XML-представление XStream для сопоставимого прокси-сервера:


    java.lang.Comparable
    
        
            
                open
                /Applications/Calculator.app
            
        
        start
    

3.3. Принудительное сравнение С использованием Сопоставимого Динамического Прокси-сервера

Чтобы принудительно сравнить с нашим Сопоставимым прокси, мы создадим отсортированную коллекцию. Давайте создадим коллекцию TreeSet , которая сравнивает два Сопоставимых экземпляра: a String и наш прокси.

Мы будем использовать конструктор копирования TreeSet для создания этой коллекции. Наконец, у нас есть XML-представление XStream для нового набора деревьев , содержащего наш прокси-сервер и Строку :


    foo
    
        java.lang.Comparable
        
            
                
                    open
                    /Applications/Calculator.app
                
            
            start
        
    

В конечном счете, атака происходит, когда XStream читает этот XML. В то время как разработчик ожидает , что XStream прочитает Person , он вместо этого выполняет атаку:

String sortedSortAttack = // XML from above
XStream xstream = new XStream();
Person person = (Person) xstream.fromXML(sortedSortAttack);

3.4. Краткое описание атаки

Давайте обобщим рефлексивные вызовы, которые делает XStream, когда он десериализует этот XML

  1. XStream вызывает конструктор TreeSet copy с Коллекцией , содержащей Строку “foo” и наш Сопоставимый прокси.
  2. Конструктор TreeSet вызывает наш Comparable прокси-метод compareTo , чтобы определить порядок элементов в отсортированном наборе.
  3. Наш Сопоставимый динамический прокси делегирует все вызовы методов обработчику Событий .
  4. Обработчик событий настроен для вызова метода start() в ProcessBuilder , который он составляет.
  5. ProcessBuilder разветвляет новый процесс, выполняющий команду, которую хочет выполнить злоумышленник.

4. Когда XStream Уязвим?

XStream может быть уязвим для этой атаки удаленного выполнения кода, когда злоумышленник контролирует XML, который он читает.

Например, рассмотрим REST API, который принимает входные данные XML. Если этот REST API использует XStream для чтения тел запросов XML, то он может быть уязвим для атаки удаленного выполнения кода, поскольку злоумышленники контролируют содержимое XML, отправленного в API.

С другой стороны, приложение, которое использует только XStream для чтения доверенного XML, имеет гораздо меньшую поверхность атаки.

Например, рассмотрим приложение, которое использует XStream только для чтения файлов конфигурации XML, заданных администратором приложения. Это приложение не подвержено удаленному выполнению кода XStream, поскольку злоумышленники не контролируют XML, который читает приложение (администратор).

5. Защита XStream От Атак Удаленного Выполнения Кода

К счастью, XStream представил фреймворк security framework в версии 1.4.7. Мы можем использовать фреймворк безопасности для защиты нашего примера от атак удаленного выполнения кода. Структура безопасности позволяет нам настроить XStream с белым списком типов, которые ему разрешено создавать.

Этот список будет включать только основные типы и наш Человек класс:

XStream xstream = new XStream();
xstream.addPermission(NoTypePermission.NONE);
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypes(new Class[] { Person.class });

Кроме того, пользователи XStream могут рассмотреть возможность укрепления своих систем с помощью агента самозащиты приложений во время выполнения (RASP). Агенты RASP используют инструментарий байт-кода во время выполнения для автоматического обнаружения и блокирования атак. Этот метод менее подвержен ошибкам, чем ручное создание белого списка типов.

6. Заключение

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

Эксплойт существует, потому что XStream использует отражение для создания экземпляров классов Java, идентифицированных XML-кодом злоумышленника.

Как всегда, код для примеров можно найти на GitHub .