Safeguard
Vulnerability Guides

What Is SSTI (Server-Side Template Injection)?

SSTI happens when user input is compiled as template code instead of rendered as data, often leading straight to remote code execution. Here is how to prevent it.

Daniel Osei
Security Researcher
5 min read

Server-Side Template Injection (SSTI) is a vulnerability where user-controlled input is concatenated into a template that the server then compiles and evaluates, so the input is treated as template code rather than template data. Because modern template engines can call methods, access object attributes, and reach language internals, a successful SSTI frequently escalates from information disclosure to full remote code execution on the server. The technique was formalized by PortSwigger's James Kettle in 2015 and is tracked under CWE-1336 (improper neutralization of special elements in a template engine).

The distinction that matters: rendering template.render(name=user_input) is safe because the input is a value passed into a fixed template. Building template = Template("Hello " + user_input) is catastrophic because the input becomes part of the template source. SSTI has powered high-impact, actively exploited bugs — CVE-2022-22954, an SSTI in VMware Workspace ONE Access, was added to CISA's Known Exploited Vulnerabilities catalog after attackers used it to achieve unauthenticated RCE on internet-facing identity servers.

How SSTI Works

Template engines exist to mix static markup with dynamic values. Each engine has its own expression syntax — {{ ... }} in Jinja2 and Twig, ${ ... } in Freemarker and Thymeleaf, <%= ... %> in ERB. When attacker input lands inside a template before compilation, the engine happily evaluates whatever expression the attacker writes.

Detection usually starts with a mathematical probe. Submitting {{7*7}} and getting back 49 proves the input is being evaluated, not echoed. From there the attacker pivots through the language's object graph. In Python's Jinja2, a classic chain walks from a string object up to Python's base object, enumerates its subclasses, and locates one that can spawn a process — turning a template expression into os.popen("id").read(). The exact payload differs by engine, but the pattern is universal: reach the host language, then reach code execution.

SSTI hides anywhere developers build templates dynamically — customizable email notifications, user-defined report layouts, marketing "merge tag" systems, and CMS themes are common hot spots, precisely because those features invite users to supply template-like content.

Vulnerable vs. Fixed

The root cause is constructing template source from input. The fix is to keep input strictly as data.

# VULNERABLE: user input becomes part of the template source
from jinja2 import Template

def greet(name):
    tmpl = Template("<h1>Hello " + name + "</h1>")  # SSTI: input is compiled
    return tmpl.render()
# greet("{{7*7}}") -> "<h1>Hello 49</h1>"  (evaluated, not escaped)
# FIXED: fixed template, input passed as a bound variable
from jinja2 import Environment, select_autoescape

env = Environment(autoescape=select_autoescape())
TEMPLATE = env.from_string("<h1>Hello {{ name }}</h1>")  # source is constant

def greet(name):
    return TEMPLATE.render(name=name)  # input is data, auto-escaped
# greet("{{7*7}}") -> "<h1>Hello {{7*7}}</h1>"  (rendered literally)

The fixed version never lets user input reach template source; it only ever flows in as a bound variable. Autoescaping then neutralizes HTML metacharacters, closing the door on reflected XSS as a bonus.

Prevention Checklist

  • Never build template source from user input — pass all dynamic values as context variables to a static template.
  • If users must supply templates, run a sandboxed engine (for example, Jinja2's SandboxedEnvironment) and treat the sandbox as a hardening layer, not a guarantee.
  • Use a logic-less templating language like Mustache for user-editable content, since it cannot execute arbitrary expressions.
  • Enable autoescaping to reduce the blast radius and stop reflected XSS in the same code path.
  • Keep template engines patched, as sandbox escapes are discovered periodically.
  • Constrain the runtime with least privilege and egress rules so an escape yields as little as possible.

How Safeguard Detects SSTI

SSTI is a runtime code-injection flaw, so proving it requires sending crafted expressions and observing evaluated output. Safeguard's dynamic application security testing injects engine-specific arithmetic and probe payloads into reflected parameters, then confirms exploitability from the response — separating a real, evaluated {{7*7}} from a harmless echo. That evidence-based approach keeps false positives low on a bug class where guesswork is expensive.

Each confirmed finding is explained by Griffin AI, which identifies the template engine in play, maps the payload to CWE-1336, and recommends the data-not-source refactor — often opening the fix directly via automated remediation. Because the template engines themselves ship as dependencies, software composition analysis also flags versions with known sandbox-escape CVEs so you patch the engine and fix the pattern together. Teams evaluating scanner accuracy can review our head-to-head comparison of detection approaches.

Frequently Asked Questions

How is SSTI different from XSS? XSS executes in the victim's browser; SSTI executes on the server. That difference is why SSTI is usually far more severe — it can lead to remote code execution, data theft from the backend, and lateral movement, whereas XSS is confined to client-side context.

Does {{7*7}} returning 49 always mean RCE? It means the input is being evaluated server-side, which is a genuine vulnerability. Whether it reaches full code execution depends on the engine and any sandboxing. Even without RCE, evaluation typically leaks internal objects and configuration, so it should always be fixed.

Are logic-less templates immune? Largely, yes — engines like Mustache cannot evaluate arbitrary expressions, so user-supplied Mustache templates cannot reach the host language the way Jinja2 or Freemarker can. They are the safest choice when you must let users author templates.


Curious whether any input in your app is being compiled as template code? Sign up free or explore detection details in the Safeguard docs.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.