Jinja server-side template injection (SSTI)

Jinja server-side template injection

Description

Passing externally controlled text as Jinja template source can cause server-side template injection (SSTI). An attacker can inject expressions or statements such as {{ ... }} and {% ... %} for the server to evaluate. The impact depends on the data, globals, and callable objects exposed to the template.

Jinja autoescaping protects the HTML context of rendered output. It does not prevent SSTI when untrusted text is first interpreted as template source.

Potential impact

  • Data or configuration exposed to the template may be disclosed.
  • Methods with side effects may be called on exposed objects, potentially leading to code execution under some conditions.
  • Even a small template may produce large output or consume substantial CPU and memory.

Remediation

  • Keep template source under developer control. Load fixed templates by name or file, and pass untrusted values only as context data.
  • Do not build template source from request data or pass it to string-rendering APIs such as render_template_string(...) or stream_template_string(...). Removing delimiters, HTML escaping, or using a function merely named sanitize is not a sufficient defense.
  • Use an allow-list only to select complete, developer-authored templates. Do not try to make arbitrary user-authored templates safe with a character allow-list.
  • If user-authored templates are required, first consider a dedicated language with restricted functionality. If Jinja is necessary, use SandboxedEnvironment or ImmutableSandboxedEnvironment, expose minimal data without methods that have side effects, handle rendering errors, and limit CPU, memory, and output in an isolated service. Jinja sandboxing alone does not provide complete isolation.

Examples

Before

python
from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.get("/greeting")
def bad_greeting():
    return render_template_string(request.args["template"])

After

python
from flask import Flask, render_template, request

app = Flask(__name__)

@app.get("/greeting")
def safe_greeting():
    return render_template(
        "greeting.html",
        name=request.args.get("name", ""),
    )

templates/greeting.html is a fixed file deployed with the application.

html
Hello {{ name }}

Explanation:

  • Before: The request value becomes template source, allowing an attacker to define Jinja syntax.
  • After: The deployed file supplies the template source; the request value is passed only as name context data.

Template source and data

Pass a developer-controlled template to source=. Keep user values in separate context data instead of concatenating them into the template string.

python
from flask import request
from jinja2 import Environment

env = Environment()


def unsafe_keyword_source():
    return env.from_string(globals={}, source=request.args["template"])


def safe_keyword_context():
    return env.from_string(
        globals={"name": request.args["name"]}, source="Hello {{ name }}"
    )

The second call keeps the template string fixed and passes the user value as data. If the output is served as HTML, also apply output-context protections such as autoescaping.

Limitations

Services that permit user-authored templates need to review exposed data, callable objects, exception handling, resource limits, and isolation whether or not they use a sandbox. Fixed templates still require protections appropriate to output contexts such as HTML.

References