Interactive Playground
Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.
pathfinder scan --ruleset python/PYTHON-FLASK-SEC-014 --project .About This Rule
Understanding the vulnerability and how it is detected
This rule detects Server-Side Template Injection (SSTI) in Flask applications where user-controlled input from HTTP request parameters flows into render_template_string() as part of the template source string. Unlike render_template() which loads templates from files, render_template_string() compiles and renders a Jinja2 template from a string argument at request time. When user input is embedded in that template string before rendering, attackers inject Jinja2 template directives that execute arbitrary Python code on the server.
SSTI in Jinja2 is especially powerful because Jinja2 templates have access to Python's object model. Classic SSTI payloads traverse the class hierarchy: {{''.__class__.__mro__[1].__subclasses__()}} to find and instantiate classes that provide file access, subprocess execution, and network connectivity. More targeted payloads reach os.system() or subprocess.Popen() directly through the template's global context.
The vulnerability occurs when developers use render_template_string() to dynamically compose templates (e.g., inserting a user-supplied greeting into a template string before rendering). The correct pattern is to pass user data as keyword arguments to render_template_string() or render_template() where Jinja2 handles it as data, not as template syntax: render_template_string('<h1>{{ name }}</h1>', name=user_name).
The taint analysis traces data from Flask request sources through string operations and function calls to the template string argument of render_template_string() at position 0. There are no recognized sanitizers because there is no transformation that makes user input safe to use as Jinja2 template syntax.
Security Implications
Potential attack scenarios if this vulnerability is exploited
Full Remote Code Execution via Jinja2 Object Traversal
Jinja2's template syntax provides access to Python objects and their attributes. A minimal SSTI payload like {{7*7}} confirms the injection. A full RCE payload traverses __class__.__mro__[1].__subclasses__() to find Popen or subprocess.check_output and executes arbitrary OS commands. The template engine runs with the same privileges as the Flask application, meaning a successful SSTI compromises the entire server.
Secret Key and Credential Extraction
Jinja2 templates have access to the application's config object through the g, app, and config template globals. An attacker can extract Flask's SECRET_KEY, database connection strings, API keys, and OAuth secrets directly from the template context: {{config.SECRET_KEY}} or {{config['DATABASE_URL']}}. These can be used to forge session cookies, access databases, and impersonate the application to external services.
Authentication Bypass via Forged Session Cookies
Flask's session is signed with SECRET_KEY using itsdangerous. An attacker who extracts SECRET_KEY via SSTI can forge arbitrary session cookies, impersonating any user including administrative accounts. This turns an SSTI finding into a complete authentication bypass without needing separate session vulnerabilities.
Persistent Backdoor via Module Modification
SSTI payloads can modify Python modules loaded in the current interpreter. An attacker can inject code that patches application functions in memory, creating a persistent backdoor in worker processes that survives across requests without touching the filesystem.
How to Fix
Recommended remediation steps
- 1Never interpolate user input into the template string before passing it to render_template_string() -- always keep the template string as a hardcoded literal.
- 2Pass user-supplied values as keyword arguments to render_template() or render_template_string() -- Jinja2 treats these as data values and applies autoescaping, not as template syntax.
- 3Use render_template() with file-based templates (.html files in the templates/ directory) as the default pattern -- file-based templates cannot be modified by request parameters at runtime.
- 4If dynamic template generation is required (e.g., CMS-style templates stored in the database), use Jinja2's SandboxedEnvironment which restricts access to dangerous attributes and built-ins.
- 5Audit all render_template_string() calls and verify the template string argument is a hardcoded literal with no request parameter interpolation.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
Scope: global (cross-file taint tracking across the entire project). Sources: Flask HTTP input methods -- request.args.get(), request.form.get(), request.values.get(), request.get_json() -- which can deliver attacker-controlled strings containing Jinja2 template syntax ({{ }}, {% %}, {# #}). Sinks: render_template_string(). The .tracks(0) parameter targets argument position 0, the template source string. This is the argument where SSTI occurs -- when user input becomes part of the Jinja2 template that is compiled and rendered. The keyword variable arguments (name=value, etc.) are not tracked because they are safe template variables, not template source. Sanitizers: None. There is no transformation that makes user input safe to embed in Jinja2 template source code. Jinja2's SandboxedEnvironment reduces the impact of SSTI but does not prevent template directive injection, so it is not recognized as a sanitizer. The rule follows tainted strings through f-string construction, concatenation, format() calls, and cross-file function calls where template strings are assembled before being passed to render_template_string().
Compliance & Standards
Industry frameworks and regulations that require detection of this vulnerability
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Flask Code Injection via eval()
User input from Flask request parameters flows to eval(). Replace with ast.literal_eval() for data parsing or json.loads() for structured input.
Flask Code Injection via exec()
User input from Flask request parameters flows to exec() or compile(). exec() cannot be safely sanitized -- redesign the feature to avoid dynamic code execution.
psycopg2 SQL Injection via String Formatting
SQL query built with string formatting passed to psycopg2 cursor.execute() enables SQL injection. Use parameterized queries with %s placeholders.
Frequently Asked Questions
Common questions about Flask Server-Side Template Injection (SSTI)
New feature
Get these findings posted directly on your GitHub pull requests
The Flask Server-Side Template Injection (SSTI) rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.