SSTI Detection
Server-side template injection occurs when user input is embedded in a template string and evaluated by the template engine. The result is often remote code execution since template engines have access to Python, Java, or PHP internals. Detection confirms evaluation is happening: engine identification determines the exploitation payload. Confirmed SSTI moves to SSTI exploitation.
- Initial probe sends
{{7*7}}and checks if the response contains 49 instead of the literal string. - Engine identification uses
{{7*'7'}}to distinguish Jinja2 (49) from Twig (7777777). - Alternate delimiters test
${7*7},#{7*7}, and<%= 7*7 %>for Java, Ruby, and ERB engines. - tplmap automation detects the engine and confirms exploitability across all major template engines.
Target parameters that appear in rendered output: name, message, subject, query, template, search, greeting. Any parameter whose value appears reflected in the page is a candidate.
Initial Probe
Send a mathematical expression wrapped in template delimiters. If the result is evaluated rather than reflected literally, template injection is confirmed:
49
49
The response containing 49 instead of {{7*7}} confirms template evaluation. If the literal string {{7*7}} appears in the output, the template engine is not processing input or a different delimiter is needed.
Engine Identification
Different template engines use different delimiters and syntax. Walk through these probes in order to narrow down the engine. Each step eliminates a group of engines based on how the application handles the input.
Step-by-Step Decision Tree
1. Test ${{7*7}} : if the response contains 49, the engine uses dollar-brace syntax. This points to Freemarker or Thymeleaf (Java). Differentiate by sending ${{"freemarker.template.utility.Execute"?new()("id"}}: Freemarker executes it, Thymeleaf returns an error.
2. Test {{7*7}} : if 49 appears, the engine uses double-brace syntax. Send {{7*'7'}} next. If the result is 49, the engine is Jinja2 (Python multiplies int by string). If the result is 7777777, the engine is Twig (PHP repeats the string). If the result is an error or empty, test for Handlebars by sending {{this}}: Handlebars renders the current context object.
3. Test #{{7*7}} : if 49 appears, the engine uses hash-brace syntax. This indicates Pug/Jade (Node.js) or Ruby ERB. Send #{{self}} next: Pug returns a JavaScript object representation, ERB returns a Ruby object string.
4. Test <%= 7*7 %> : if 49 appears, the engine uses ERB-style delimiters. This is Ruby ERB or EJS (Node.js). Send <%= system('id') %> to confirm ERB. EJS uses <%= process.version %> instead.
Once the engine is confirmed, proceed to engine-specific exploitation payloads.
7777777
Quick Reference Table
Engine identification from the response:
Payload | Response | Engine |
|---|---|---|
{{7*7}} | 49 | Jinja2, Twig, Handlebars, or Nunjucks |
{{7*'7'}} | 49 | Jinja2 (Python) |
{{7*'7'}} | 7777777 | Twig (PHP) |
${7*7} | 49 | Freemarker or Thymeleaf (Java) |
#{7*7} | 49 | Pug/Jade (Node.js) or Ruby ERB |
<%= 7*7 %> | 49 | ERB (Ruby) or EJS (Node.js) |
Test Multiple Delimiter Formats
If the standard Jinja2 delimiter does not work, try alternates used by other engines:
{{7*7}} ->
${7*7} -> 49
#{7*7} ->
<%= 7*7 %> ->
[[7*7]] ->
{7*7} ->
${7*7} evaluating to 49 indicates a Java-based template engine. Use tplmap to automate engine identification and exploitation:
[+] Tplmap 0.5
[*] Testing if GET parameter 'name' is injectable
[+] Jinja2 plugin is testing rendering with tag '{{'
[+] Jinja2 rendered with tag '{{'
[+] Jinja2 is vulnerable!
References
-
PortSwigger: SSTITOOL: PortSwigger: SSTI, portswigger.net/web-security/server-side-template-injection (opens in new tab)
Detection methodology and engine identification decision tree
-
Automated SSTI detection and exploitation tool