Server-side template injection (SSTI) happens when user input is embedded into a template that the server then renders, so the input is evaluated as template code rather than data. Because templates can reach into the host language, SSTI often escalates from reflected output to remote code execution, with the exact ceiling set by the engine and its sandbox.
The first step is detecting the engine, because each has its own expression syntax. A mathematical probe that renders differently per engine narrows it down: {{7*7}} (returns 49) points at Jinja2, Twig, or Nunjucks; ${7*7} at FreeMarker or a JSP/EL context; #{7*7} at Pug or a JSF context; <%= 7*7 %> at ERB; #set($x=7*7)$x at Velocity; @(7*7) at Razor; [% 7*7 %] at Template Toolkit. A polyglot such as ${{<%[%'"}}%\ triggers a revealing error in several engines. When 7*7 renders as 49 the input is being evaluated, not just reflected.
The escalation pattern is shared: from a template expression, walk the object graph or built-ins the engine exposes to reach the language runtime (a class loader, a globals dictionary, a utility that executes commands), then call out to the OS. Whether that succeeds depends on the sandbox: some engines run untrusted templates in a restricted mode by default (and the task is to escape it), while others, and most engines when the framework does not enable sandboxing, evaluate freely.
This area is organized by language, then by engine, since the gadgets are engine-specific.
Languages#
- Python: Jinja2, Mako.
- PHP: Twig, Smarty.
- Java: FreeMarker, Velocity.
- JavaScript: Handlebars, Pug.
- Ruby: ERB.
- Go: text/template and html/template.
- C#: Razor.
- Rust: Tera.
- Perl: Template Toolkit.
References#
- PortSwigger Web Security Academy: Server-side template injection
- OWASP Testing Guide: Testing for Server-Side Template Injection