flawopen.com/Vulnerabilities/Server-Side Template Injection

Server-Side Template Injection (SSTI)

Critical RCE CWE-1336 Modern Web & Cloud
ELI5 — The Form Letter that Reads Secret Formulas

Imagine a mail-order company printing letters: 'Dear {{ name }}, thank you for your order.' Normally, the printer inserts customer names like 'Alice'. But a hacker types '{{ 7 * 7 }}' into the name box, and the printer outputs '49'. Seeing that the printing machine calculates math, the hacker submits a command asking the printer to open the vault door and dump the cash. In SSTI, the template engine doesn't just print text—it executes attacker code inside the web server's runtime.

Target: Jinja2 (Python), Twig (PHP), Freemarker/Thymeleaf (Java), ERB (Ruby)
Vector: Template expressions: {{ 7*7 }}, ${7*7}, #{7*7}
Impact: Arbitrary Python/Java sandbox escape, remote command execution on host
Remediation: Separating static templates from dynamic variables, disabling template eval

The Mechanism & Root Cause

Developers accidentally build template source strings dynamically by concatenating user input (e.g. render_template_string(f'Hello {username}')) instead of passing the input as a template parameter dictionary (e.g. render_template('hello.html', name=username)). The template engine evaluates user input as code, allowing attackers to access Python's object hierarchy (__mro__) to instantiate subprocess.Popen.

flask_app.py (Vulnerable)Vulnerable
# VULNERABLE: Rendering dynamically compiled template string
from flask import Flask, request, render_template_string
app = Flask(__name__)

@app.route('/greet')
def greet():
    name = request.args.get('name', 'Guest')
    # Attacker passes: {{ cycler.__init__.__globals__.os.popen('id').read() }}
    template = f"

Welcome to the portal, {name}!

" return render_template_string(template) # RCE on web server!
flask_app.py (Hardened)Hardened
# HARDENED: Static template file with untrusted input passed as variable
from flask import Flask, request, render_template
app = Flask(__name__)

@app.route('/greet')
def greet_safe():
    name = request.args.get('name', 'Guest')
    # Static template file treats 'name' strictly as a data string, never executable syntax
    return render_template('greet.html', name=name)

# In greet.html:
# 

Welcome to the portal, {{ name }}!

The Attack & Exploit Sequence

Defensive Engineering & Prevention Rules

Explore related security topics and post-mortems: Complete Security Directory →