
Отчёт об исследовании уязвимостей S2-045, S2-055 в Struts2 и уязвимостей CVE-2017-7525, CVE-2017-15095 в Jackson
Мы опубликовали краткое резюме, которое легко читается. Рекомендуется тем, кто хочет сначала понять суть, или тем, у кого мало времени.
1 декабря 2017 года было опубликовано обновление безопасности для Struts2. Ещё до публикации в списке рассылки ходили разговоры о том, что это связано с уязвимостью в Jackson (популярной JSON-библиотеке для Java), и автор, который использует Jackson в корпоративных системах и инструментах, также был заинтересован в конкретном содержании.
Фактически опубликованное содержание включало исправления следующих двух проблем безопасности. Только S2-055 затрагивает уязвимость jackson-databind, компонента Jackson.
В REST plugin, по-видимому, ранее были встроены как обработчик на основе JSON-lib, так и обработчик на основе Jackson, и пользователь мог выбирать между ними. В S2-054 обработчик по умолчанию был переключён на Jackson, а в S2-055 версия Jackson, которая была старой, была обновлена до последней — такова, по-видимому, полная картина данного исправления.
Итак, что же такое уязвимость CVE-2017-7525? Поскольку сам автор часто использует Jackson при обработке JSON на Java, он решил исследовать эту проблему в выходные 2–3 декабря — эта статья является результатом.
Среда автора, использованная для проверки примеров кода:
В блоге Адама Кодила (Adam Caudill) опубликовано объяснение CVE-2017-7525.
Если кратко пересказать своими словами, jackson-databind предоставляет функцию (ObjectMapper class) для отображения JSON в объекты Java.
Вызов ObjectMapper.enableDefaultTyping() позволяет выполнять отображение по именам классов, встроенным в JSON.
Думаю, некоторые из вас уже почувствовали нехорошее предчувствие при мысли «можно задавать имена классов из входного JSON» — и именно это дурное предчувствие сбылось в CVE-2017-7525.
Прежде чем перейти к объяснению уязвимости, объясним, зачем вообще была реализована такая функция.
О базовом использовании десериализации с помощью jackson-databind смотрите следующий пример кода. (В этой статье для примеров кода Jackson используется Groovy. Удобно легко переключать версию jackson-databind с помощью @Grab.)
В приведённом выше примере кода ключ "animal" просто может быть отображён на класс Animal. А как обстоит дело в следующем случае?```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
В этой конфигурации появляются два случая: когда содержимое ключа "animal" ссылается на класс Dog и когда на класс Cat. Следовательно, требуется дополнительная информация о том, какой класс использовать для маппинга.
Чтобы решить эту проблему, jackson-databind встроил собственную обработку, позволяющую встраивать имя класса для маппинга в JSON.
Например, как показано ниже, содержимое ключа "animal" помещается в массив, а в первом элементе указывается имя класса.```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
Это позволяет ObjectMapper.readValue() распознавать содержимое ключа "animal" как класс Dog и выполнять маппинг.
Конечно, без дополнительных настроек невозможно определить, было ли содержимое ключа "animal" изначально массивом или содержало информацию об имени класса, специфичную для jackson-databind.
За переключение этого режима отвечает метод ObjectMapper.enableDefaultTyping().
Существует также способ определения с помощью аннотации @JsonTypeInfo в классе. Подробнее см. документацию Jackson по ссылке:
Ниже приведён пример кода с использованием метода ObjectMapper.enableDefaultTyping():
Как было показано выше, передача имени класса и его свойств в JSON позволяет, хотя и с определёнными ограничениями, создавать экземпляры произвольных классов с произвольными свойствами.
Именно эта уязвимость используется в CVE-2017-7525, и отправной точкой, вероятно, послужил следующий отчет:
В нём сообщается об опасности выполнения произвольного кода через манипуляции с именами классов в популярных библиотеках сериализации/десериализации Java, таких как Jackson, и перечислены конкретные опасные классы.
Неизвестно, связано ли это напрямую, но по датам вскоре после первого коммита в указанном репозитории был создан следующий Issue в jackson-databind, и началась работа над исправлением:
Как же выглядят данные JSON и код Java, эксплуатирующие эту уязвимость?
Подсказку можно найти в тестовом коде jackson-databind 2.8.9, в котором было выполнено исправление:
На основе этого тестового кода был подготовлен пример для проверки работы: