Разберите обработчик, в котором пользователь задаёт содержимое шаблона. Какой основной риск должен подтвердить тестировщик?
function preview(request):
template = request.query["template"]
return render_template(template, {"name": "Alex"})
Основной риск — серверная инъекция шаблона (SSTI). Пользователь управляет не только данными, но и текстом, который движок интерпретирует как шаблонный код; в зависимости от движка и настроек это может привести к раскрытию данных, обходу ограничений или выполнению команд на сервере.
Тест следует начинать с безвредного выражения, характерного для используемого шаблонизатора, например {{7*7}}. Если в ответе появляется 49, а не исходный текст, пользовательский ввод исполняется как шаблон, что подтверждает уязвимость.
Шаблонизаторы появились, чтобы отделить представление страницы от прикладной логики и удобно подставлять данные в заранее подготовленные документы. Безопасная модель предполагает, что шаблон принадлежит приложению, а пользователь передаёт только значения для его переменных.
Проблема возникает, когда эти роли смешиваются: недоверенная строка становится самим шаблоном. Тогда обычный механизм генерации представления превращается в интерпретатор пользовательских инструкций.
В примере параметр template передаётся первым аргументом render_template, поэтому он, предположительно, определяет исходный шаблон. Если функция компилирует этот аргумент и затем выполняет выражения, атакующий может обратиться к объектам контекста, внутренним функциям или ресурсам сервера.
Последствия зависят от возможностей движка и его окружения. Это может быть чтение секретов из контекста или файлов, подмена содержимого ответа, раскрытие конфигурации, а при опасных расширениях — выполнение произвольных операций на сервере.
Сначала нужно установить, действительно ли функция интерпретирует ввод как шаблон, а не просто вставляет его в заранее заданную страницу. Для этого используются безопасные диагностические выражения и маркеры, например арифметическое выражение синтаксиса конкретного движка. Простое отражение строки {{7*7}} без вычисления не доказывает SSTI.
После подтверждения следует определить границы воздействия в тестовой среде: доступен ли контекст запроса, можно ли обращаться к атрибутам объектов, разрешены ли вызовы функций и дополнительные расширения. Проверка чтения файлов или выполнения команд допустима только с явным разрешением и безопасными тестовыми ресурсами; для подтверждения уязвимости обычно достаточно контролируемого вычисления.
Надёжное исправление — не принимать шаблон от пользователя. Приложение должно выбрать шаблон из фиксированного набора, а пользовательское значение передать как данные:
Экранирование HTML полезно против XSS в выводе, но не устраняет SSTI, если пользовательская строка сначала становится исходным текстом шаблона. Песочница шаблонизатора снижает последствия, однако не должна считаться единственной защитой: ошибки конфигурации, новые расширения и обходы ограничений могут снова открыть опасные возможности.
Сервис предпросмотра писем позволял менеджеру вводить шаблон сообщения. Рассматривались три варианта: полностью запретить пользовательские шаблоны, разрешить их в песочнице или оставить шаблоны фиксированными, предоставив ограниченный набор переменных. Полный запрет был самым простым, но ухудшал функциональность; песочница сохраняла гибкость, однако требовала постоянного контроля движка и расширений.
Выбрали фиксированный шаблон с белым списком переменных: приложение само выбирало файл шаблона, а введённые значения передавались как данные и экранировались в соответствующем контексте. Проверка с арифметическим выражением после исправления показывала его как обычный текст, а попытки обратиться к переменным шаблона не выполнялись. Функциональность сохранилась, а граница между кодом шаблона и данными стала контролируемой.
Чем SSTI отличается от XSS?
XSS обычно выполняется в браузере жертвы, когда пользовательское значение попадает в HTML, JavaScript или другой клиентский контекст. SSTI выполняется на сервере во время обработки шаблона; XSS может быть её последствием, но эти уязвимости проверяются на разных этапах и имеют разные границы воздействия.
Достаточно ли запретить символы шаблонного синтаксиса?
Нет. Фильтрация отдельных последовательностей хрупка: у разных движков различаются синтаксис, альтернативные конструкции и способы доступа к объектам. Кроме того, такой фильтр может ломать легитимные данные. Надёжнее исключить пользовательский ввод из позиции шаблона и передавать его только через контекст данных.
Всегда ли подтверждённая SSTI означает выполнение команд на сервере?
Нет. Возможности зависят от движка, режима безопасности, доступного контекста и прав процесса. Иногда уязвимость ограничивается вычислением выражений или раскрытием данных. Тем не менее сам факт исполнения недоверенного шаблона уже является дефектом границы доверия, а потенциальное выполнение команд нужно проверять отдельно и безопасно.