Server-Side Template Injection (SSTI) occurs when user input is embedded into a server-side template and evaluated by the template engine. Unlike XSS, SSTI executes on the server — making it one of the most impactful web vulnerabilities, often leading directly to remote code execution. Bike demonstrates SSTI in a Node.js application using the Handlebars template engine, alongside Burp Suite for interception and payload delivery.
| Skill | Why it matters |
|---|---|
| Detecting SSTI via template probing | A payload like 7*7 inside template delimiters confirms the vulnerability in seconds |
| Template engine fingerprinting | Jinja2, Handlebars, Twig, EJS all require different exploit payloads |
| Burp Suite Proxy interception | Modifying POST parameters with special characters requires Burp to avoid encoding issues |
| Handlebars SSTI to RCE | Node.js process chain gives OS command execution from template context |
A Node.js/Express application on port 80. Browsing to the app reveals a form that accepts an email address and reflects it back on the page — any field that reflects user input through a template is a candidate for SSTI.
Submit template-syntax expressions in the email field and observe whether they are evaluated or echoed literally.
If the response shows 49, the input was evaluated. If an error message appears mentioning the engine name, that is also confirmation — template engines only throw errors on input they attempt to parse.
| Payload | Jinja2 (Python) | Handlebars (Node.js) | Twig (PHP) |
|---|---|---|---|
| {{7*7}} | 49 | 49 or engine error | 49 |
| {{7*'7'}} | 7777777 (string repeat) | Type error | 49 |
| ${7*7} | Echoed literally | Echoed literally | Echoed literally |
The Bike application error response directly names Handlebars in the stack trace. This immediately tells you which exploit chain to use.
Intercept the POST request in Burp Suite. Replace the email value with the Handlebars payload. This payload navigates the JavaScript prototype chain to reach the Function constructor, then calls Node's child_process.execSync().
Swap the id command in the payload for the flag read command:
| Engine | Stack | RCE method |
|---|---|---|
| Jinja2 | Python / Flask | Class chain to __import__('os').popen() |
| Handlebars | Node.js | Prototype chain to child_process.execSync() |
| Twig | PHP / Symfony | _self.env.registerUndefinedFilterCallback + getFilter |
| Freemarker | Java | "freemarker.template.utility.Execute"?new()("id") |
| EJS | Node.js | outputFunctionName parameter injection |
| Pebble | Java | class.forName("java.lang.Runtime") reflection chain |
SSTI is SQL injection for the rendering layer. When user input reaches a template engine without sandboxing, the attacker gets the full power of the underlying runtime — running on the server, not in the browser.
| Concept | Real-world relevance |
|---|---|
| Any reflected input is a candidate | Forms, URL params, HTTP headers, cookies — all should be probed for template injection |
| Errors reveal engine identity | Stack traces and error messages from Node.js, Python, and Java apps name the template engine directly |
| SSTI runs on the server | Unlike XSS, the payload executes server-side with the web process privileges — often root or SYSTEM |
| Burp is essential for delivery | Template payloads contain curly braces and special characters that browsers encode — always inject via Burp |
