Server-side template injection (SSTI)
CWE-1336OWASP A03:2021Updated August 31, 20265 min read
Server-side template injection (SSTI) is a vulnerability where attacker input is evaluated by the server's template engine, because the input is built into the template source instead of passed as data. On engines like Jinja2, Twig, or Freemarker this usually escalates to remote code execution. The fix is to keep input out of the template and pass it as a context variable.
Template engines turn a template plus some data into finished HTML, email, or configuration. Server-side template injection turns that convenience into one of the most dangerous injection flaws there is, because the same engine that formats a page can also run code. Here is what it is, how an attack unfolds, and how to keep user input out of the template itself.
What is server-side template injection?
Server-side template injection, usually abbreviated to SSTI, is a vulnerability where an attacker gets their own template expressions evaluated by the server’s template engine. It happens when user input is concatenated into the template source instead of being passed to the template as data. Engines such as Jinja2 (Python), Twig (PHP), and Freemarker (Java) are built to evaluate expressions, so any input that reaches the source is treated as logic, not text.
An everyday comparison: a template is a fill-in-the-blank form letter. Normally you write the letter and only the reader’s name goes in the blank. SSTI is what happens when you let the reader write part of the letter itself: they can add instructions the printer will faithfully carry out. The engine cannot tell your wording from theirs, so whatever they write becomes part of the program.
The flaw is not in the engine, it is in building the template from untrusted input. That single mistake is what separates a harmless greeting from full server compromise.
How does an SSTI attack work?
The root cause is the same as every injection flaw: input and code are assembled into one string. A greeting endpoint that builds the template from the name parameter looks like this.
Vulnerable:
from flask import request
from jinja2 import Template
@app.route("/greet")
def greet():
name = request.args.get("name", "")
# The user's input becomes part of the template SOURCE
source = "<h1>Hello " + name + "</h1>"
return Template(source).render()
With a normal name this behaves. But the attacker does not send a name, they send a template expression. The classic probe multiplies two numbers inside Jinja2’s expression delimiters:
GET /greet?name={{7*7}} HTTP/1.1
Host: example.com
If the response reads “Hello 49” instead of echoing the payload literally, the input was evaluated, and the endpoint is injectable. From that foothold an attacker walks the object graph the engine exposes until they reach Python’s os module, then runs commands:
GET /greet?name={{cycler.__init__.__globals__.os.popen('id').read()}} HTTP/1.1
Host: example.com
The response now contains the output of id, which means arbitrary command execution on the server. Twig and Freemarker have different syntax (Twig uses the same double-brace probe, Freemarker a dollar-sign expression) but the escalation path is the same: reach a runtime object, then a system call.
The fix is to keep user input out of the template source entirely. Load the template from a file or a fixed string, and pass the input as a context variable. The engine then treats it as data to be printed, never as logic to be run.
Secure:
from flask import request
from jinja2 import Environment, FileSystemLoader
env = Environment(
loader=FileSystemLoader("templates"),
autoescape=True,
)
@app.route("/greet")
def greet():
name = request.args.get("name", "")
# Input is passed as data, the template is fixed
template = env.get_template("greet.html")
return template.render(name=name)
With a fixed greet.html template that prints the name variable, the same payload now renders literally as text. The delimiters were compiled once, from a file you control; the attacker’s braces arrive too late to be parsed as an expression.
What is the impact of SSTI?
Impact runs from information disclosure to full remote code execution, which is why the severity sits between high and critical. On engines with a rich runtime like Jinja2 or Freemarker, a working payload usually reaches operating-system commands, so the attacker can read files, harvest secrets and environment variables, pivot into the internal network, or install a persistent backdoor. That is a complete server takeover from a single request.
Even where the engine is more restricted, an attacker can often read arbitrary application data such as template globals, configuration and connection strings, or trigger denial of service by evaluating an expensive expression. Because the payload arrives through a normal parameter and the response can look ordinary, the intrusion is easy to miss in logs. In practice, an SSTI on a public endpoint should be treated as a critical, drop-everything finding.
How do you detect SSTI?
The standard first test is a mathematical probe inside the engine’s delimiters, sent to every field that might end up in a template: a URL parameter, a form value, a profile field, an email subject. If a value that evaluates arithmetic comes back computed rather than echoed, the input is reaching the engine. Because each engine has its own syntax, testers use a small decision tree of probes to fingerprint which engine is in use before attempting escalation.
Scanners like Burp Suite and template-focused tools such as tplmap catch the obvious reflected cases, but SSTI frequently hides in second-order sinks: a value saved now and rendered into a report, invoice, or notification email later. Tracing that path takes manual work, and it is a routine part of an AssistSec penetration test, where we confirm whether a probe actually reaches the engine and, with permission, demonstrate the real impact.
How do you prevent SSTI?
- Never build template source from user input. Load templates from files or fixed strings only; this removes the vulnerability at its root.
- Pass user input as context variables, so the engine renders it as data instead of evaluating it as logic.
- Prefer a logic-less engine such as Mustache or Handlebars where the use case allows, so templates cannot express runtime logic in the first place.
- Enable the engine’s sandbox or restricted mode as defence in depth, keep it patched, but do not rely on it as your only control.
- Separate content authors from templates. If users must supply formatting, use a narrow, allowlisted markup like Markdown, never a full template language.
- Run the renderer with least privilege, in a container with no shell and minimal network access, so a successful payload gains as little as possible.
- Test the code regularly. A focused penetration test and code review catch the injectable sinks that scanners miss.
Sources
Frequently asked questions
Is SSTI the same as cross-site scripting?
No. XSS runs in the victim's browser, while SSTI executes on the server inside the template engine. SSTI is usually far more severe because it often leads directly to remote code execution. A payload that only injects HTML is XSS; one that gets evaluated as template logic is SSTI.
Which template engines are affected by SSTI?
Any engine that evaluates expressions can be affected, including Jinja2, Twig, Freemarker, Velocity and Thymeleaf. The risk is highest on engines with a rich runtime, where escalation to command execution is straightforward. Logic-less engines such as Mustache greatly reduce the risk.
Does escaping output prevent SSTI?
No. Output escaping prevents cross-site scripting, but SSTI executes while the template is evaluated, before any output is escaped. The only reliable fix is to keep user input out of the template source and pass it as a context variable.
How do I test for SSTI quickly?
Send a simple arithmetic expression in the engine's delimiters into every field that might reach a template. If the response returns the computed result instead of the literal text, the input is being evaluated. Fingerprint the engine before attempting escalation, and only test systems you are authorised to test.
Related articles
- VulnerabilitiesCWE-78A03:2021OS command injectionOS command injection explained: how shell metacharacters reach a system call, what an attacker gains, and why argument arrays without a shell fix it.
- VulnerabilitiesCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) lets attackers inject malicious scripts into web pages that run in visitors' browsers. Learn how XSS works and how to prevent it.
- VulnerabilitiesCWE-94A03:2021Remote code execution (RCE)Remote code execution (RCE) explained: how attackers run their own commands or code on your server through unvalidated input, and how to prevent it.
- VulnerabilitiesCWE-89A03:2021SQL injectionSQL injection explained: how attackers use unfiltered input to read or change your database, what the impact is, and how prepared statements stop it.
- VulnerabilitiesCWE-918A10:2021Server-side request forgery (SSRF)Server-side request forgery (SSRF) explained: how attackers abuse your server to reach internal systems and cloud services, and how to prevent it.