Injection

SSTI: Server-Side Template Injection Explained

How template engines become code execution vectors in Flask Jinja2 and web frameworks — and how to prevent it

What Is SSTI

Server-Side Template Injection (SSTI) is a vulnerability where user-controlled input is processed by a server-side template engine as template syntax rather than as literal data. Template engines like Jinja2 (Python), Twig (PHP), and Freemarker (Java) are designed to execute expressions embedded in templates — {{ 7 * 7 }} renders as 49. When user input reaches the template engine without sanitization, attackers inject expressions that execute arbitrary code on the server.

Classified as CWE-94: Improper Control of Generation of Code and part of OWASP A03:2025 — Injection, SSTI is consistently rated Critical severity (CVSS 9.0+) because successful exploitation typically leads to Remote Code Execution (RCE) — full control of the server.

SSTI is distinct from XSS: XSS executes JavaScript in the browser (client-side), while SSTI executes code in the template engine on the server. An XSS payload steals cookies; an SSTI payload reads server files, exfiltrates environment variables, or spawns reverse shells.

How Template Engines Execute Attacker Code

Template engines are designed to evaluate expressions. In Jinja2, the exploit path traverses the Python class hierarchy to reach the subprocess module:

# The Jinja2 expression that executes OS commands:
# (written without the double-brace syntax to avoid rendering as template)
# PAYLOAD: open-brace open-brace ''.__class__.__mro__[1].__subclasses__()[396]('ls', shell=True, stdout=-1).communicate()[0] close-brace close-brace
#
# Breaking this down:
# ''.__class__            -> str class
# .__mro__[1]             -> object (base class of all Python objects)
# .__subclasses__()       -> list of all Python classes in memory
# [396]                   -> subprocess.Popen (index varies by Python version)
# ('ls', shell=True, ...)  -> execute shell command

This is why SSTI is critical: the template engine has access to the entire Python runtime. An attacker who achieves SSTI can read files, execute commands, and establish persistence.

Jinja2 SSTI in Flask

Flask uses Jinja2 as its template engine. The primary SSTI vector in Flask is render_template_string() — a function that renders a template string directly rather than loading a template file. When this function receives user input, SSTI is possible.

Vulnerable (direct user input):

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/page')
def page():
    template = request.args.get('template', '')
    return render_template_string(template)
    # Attack via URL: /page?template=PAYLOAD_WITH_JINJA2_EXPRESSION

Vulnerable (f-string interpolation):

@app.route('/greet')
def greet():
    name = request.args.get('name', 'visitor')
    template = f'<h1>Hello, {name}!</h1>'
    return render_template_string(template)
    # Attack: name=JINJA2_PAYLOAD injects into the template before rendering

Secure:

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/greet')
def greet():
    name = request.args.get('name', 'visitor')
    # SECURE: Pass name as a variable to a template file
    # Jinja2 auto-escapes variables passed to render_template()
    return render_template('greet.html', name=name)

# greet.html:
# <h1>Hello, {{ name }}!</h1>
# name is treated as data, not as template syntax

The fundamental rule: never use render_template_string() with user input. Use render_template() with template files, passing user data as template variables.

SSTI in Other Template Engines

Twig (PHP)

<?php
// VULNERABLE: User input in Twig template string
$loader = new ArrayLoader(['page' => $_GET['template']]);
$twig = new Environment($loader);
echo $twig->render('page', []);

Freemarker (Java)

// VULNERABLE: User-controlled template content
String templateContent = request.getParameter("template");
Template template = new Template("dynamic", templateContent, cfg);
template.process(model, writer);

Handlebars (JavaScript/Node.js)

// VULNERABLE: Compiling user input as a template
const source = req.query.template;
const template = Handlebars.compile(source);
const html = template(context);
// Handlebars prototypes blocked by default in v4.7.7+ but older versions vulnerable

Blind SSTI Detection

In blind SSTI, the application processes the template but does not render the result directly to the attacker. Attackers use arithmetic or timing signals to confirm SSTI:

Arithmetic confirmation

# Jinja2: if rendered value is 49, SSTI confirmed
# Payload: 7 times 7 wrapped in double curly braces
# Returns 49 (SSTI) vs literal "7 * 7" (no SSTI)

# Twig vs Jinja2 fingerprinting:
# 7 times the string '7' in double curly braces
# Returns 49 (Jinja2) or 7777777 (Twig)

Time-based confirmation

# Cause a sleep via subprocess to confirm code execution
# If response takes 5 seconds, SSTI is confirmed
# (use controlled testing environments only)

How CodeSlick Detects SSTI

CodeSlick detects the primary SSTI code patterns in Python and Go:

Python/Flask (Check #30 — CRITICAL)

CodeSlick flags render_template_string() calls that receive user-controlled input from request.*, form data, or variables named with user-input conventions. This is classified as CRITICAL severity with CWE-94 mapping.

# All of these trigger Check #30:
render_template_string(request.args.get('template'))
render_template_string(request.form['content'])
render_template_string(f"Hello {user_input}")
render_template_string(user_template)

The fix suggestion replaces render_template_string() with render_template() and passes user data as a template variable.

Go (template injection / CWE-79)

CodeSlick detects template.HTML(), template.JS(), and related unsafe type conversions with string concatenation involving user input. These bypass Go's html/template auto-escaping. Classified as High severity with CWE-79.

Detect SSTI vulnerabilities in your Python Flask applications automatically — CodeSlick flags render_template_string() with user input as CRITICAL.

Frequently Asked Questions

Related Guides