flawopen.com/SSTI/Jinja2

Is Jinja2's render_template_string() safe?

CWE-1336: Server-Side Template InjectionReference page
Short answer

Safe only when the template string is a literal you wrote in source. The moment user input is interpolated into that string — with an f-string, %, .format() or concatenation — it becomes server-side template injection, which in Flask reliably escalates to remote code execution.

VULNERABLE
# All four are the same bug
render_template_string(f"Hi {name}")
render_template_string("Hi " + name)
render_template_string("Hi %s" % name)
render_template_string("Hi {}".format(name))

# A very common real-world shape:
@app.errorhandler(404)
def not_found(e):
    return render_template_string(
        f"<h1>No such page: "
        f"{request.path}</h1>"
    ), 404
# The URL path is attacker-controlled.
FIXED
# Template is a literal; the user value
# arrives as a named context variable
render_template_string(
    "Hi {{ name }}", name=name
)

# Better still, a real template file
render_template("greeting.html",
                name=name)

# For the 404 case, no template
# evaluation is needed at all:
return render_template("404.html",
                       path=request.path), 404

Why Flask makes this severe

Jinja2 expressions can access attributes on any object in scope. Flask's default rendering context exposes application globals — typically including config, request, and session. From any Python object an attacker can walk the class hierarchy to reach loaded modules and from there invoke arbitrary code. The well-known probe {{ 7*7 }} returning 49 confirms evaluation; escalation from that point is a solved problem with public tooling.

A frequently overlooked consequence is that {{ config }} alone dumps the Flask configuration, which commonly contains SECRET_KEY and database credentials — so even a partially constrained injection is immediately damaging.

Where this tends to appear

Rarely in a main page handler, where developers use template files. It shows up in error handlers that echo the requested path, admin features that render user-authored email or notification templates, multi-tenant "custom branding" features, and debug or preview endpoints that were never intended to ship.

FAQ

Does autoescape protect against this?

No. Autoescaping escapes rendered values to prevent XSS in the output. It does not prevent a user-supplied string from being compiled as template source. The two controls address different stages.

What if I need users to supply templates, for email customisation?

Use Jinja2's SandboxedEnvironment rather than the default, accept that sandbox escapes have been found historically, and render in an isolated process with minimal privileges. If the use case allows it, a logic-less engine such as Mustache is a substantially safer choice.

How do I find these in an existing codebase?

Grep for render_template_string and inspect every call site for an f-string, +, % or .format() in its first argument. A literal string with {{ }} placeholders and keyword arguments is the safe shape.

References