Server-side
Server-Side Template Injection (SSTI): The Complete Practical Guide
What SSTI is, how to detect it, fingerprint the engine, exploit Jinja2, Twig, and Freemarker to RCE, and prevent it. With free labs to practice on.
Published

Server-side template injection happens when an application takes your input and puts it into a server-side template that the engine then evaluates, instead of passing it in as a plain value. The engine does not know your input was meant to be data, so it runs it as template code. From there you can reach the engine's object model, and on most capable engines that is a direct road to running commands on the server. SSTI is one of the most reliable routes to remote code execution, and it hides in places developers rarely think of as templates: emails, PDFs, error pages, even filenames. Want to feel it rather than read about it? Sign up free and try it on a live target.
What is SSTI?
The mental model in one sentence: the application meant your input as a value, but the engine treats it as template syntax. A template is a document with placeholders that the engine fills in, like a greeting that inserts a username. That is safe when the username is passed to the engine as data. It becomes SSTI when the username is concatenated into the template string itself, because now anything you type is template code the engine will execute, including expressions that reach into the host language.
That is the whole bug, and it is why SSTI is so much more dangerous than it first looks. What is SSTI covers template engines, the variable-versus-template distinction, and the many places SSTI hides.
Why it happens
The vulnerable pattern is a developer building the template by string formatting instead of passing a context variable:
# vulnerable: user input is concatenated INTO the template source
from jinja2 import Template
Template("Hello " + request.args["name"]).render()# safe: the name is passed as data, never parsed as template code
Template("Hello {{ name }}").render(name=request.args["name"])In the first, your name becomes part of the template and is evaluated. In the second, it is just a value. The entire topic is the gap between those two lines.
Detection and confirmation
You confirm SSTI by sending a small expression in template syntax and checking whether the server computed it. The classic probe is a multiplication: if a field that echoes your input turns the 7-times-7 expression into 49, something evaluated it server-side. The work is ruling out coincidence and client-side rendering, which detection and confirmation walks through with the detection polyglot and per-syntax probes.
Identifying the engine
Once you know evaluation happens, you fingerprint the engine, because the exploit path is engine-specific. The key discriminator is a string-multiplication probe: multiplying a number by a quoted digit renders as a repeated string in Python-based Jinja2 but as a plain number in PHP-based Twig, which tells them apart in a single request. Identifying the engine gives the full decision tree.
Per-engine exploitation
Each engine reaches code execution its own way:
- Jinja2 (Python): walk the object model to
os.popen. See exploiting Jinja2. - Twig and PHP engines: version-specific filter and callback tricks. See exploiting Twig and PHP.
- Freemarker and Velocity (Java): built-ins and reflection to
Runtime.exec. See exploiting Freemarker and Velocity.
SSTI to RCE and sandbox escape
Capable engines expose the host language's object graph, so the move from "it evaluates math" to "it runs commands" is about finding a reachable path to an execution primitive, and defeating any sandbox in the way. SSTI to RCE and sandbox escape covers walking the object graph and escaping Jinja2 and Twig sandboxes.
Blind SSTI
Often the template renders somewhere you never see: an email, a generated PDF, an async job. Blind SSTI covers confirming with a timing expression or an out-of-band callback, and exfiltrating output when nothing comes back in the response.
A worked example
Say a profile page greets you with a display name you control, and the greeting is built by concatenating your name into a Jinja2 template. You submit the 7-times-7 expression as your name and the page greets "Hello 49", so the server evaluated it. You confirm the engine with a string-multiplication probe and get a repeated string, pointing at Jinja2:
POST /profile HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
Cookie: session=...
display_name={{cycler.__init__.__globals__.os.popen('id').read()}}HTTP/1.1 200 OK
Content-Type: text/html
<h1>Hello uid=33(www-data) gid=33(www-data) groups=33(www-data)
</h1>The greeting now contains the output of id. That is SSTI to remote code execution: probe, fingerprint, then reach an execution primitive through the engine's object model.
Prevention
- Never render user input as a template. Pass it to the engine as a context variable so it is treated as data, not code. This is the real fix.
- Prefer logic-less templates (Mustache-style) for anything touching user input, since they have no object model to walk.
- Enable the engine sandbox and configure it strictly, but treat it as defense in depth, not a guarantee.
- Remember the non-obvious sinks: emails, PDFs, filenames, error pages, and themes are templates too.
- Patch the engine and remove dangerous built-ins you do not use.
Full detail is in preventing SSTI.
Where it connects
SSTI lands at the same destination as OS command injection, remote code execution on the host, just reached through a template engine instead of a shell. It also looks deceptively like cross-site scripting: both reflect your input, but XSS runs in the browser while SSTI runs on the server, and the math probe is how you tell which one you have found.
Where to go next
Work through the cluster in order, or jump to what you need. Start with what is SSTI if the idea is new, then detection and confirmation and identifying the engine. When you want every payload in one place, the SSTI cheat sheet has them. And when you are ready to break something real, sign up free and start hacking.