1. Server code: Unescaped variable enters template engine in Python code
1.A. render_template_string()
with string formatting
render_template_string()
renders a Jinja2 template directly from a string.
If the template is modified in any way, such as with string formatting, it creates
a potential server-side template injection. Using render_template()
is strictly
safer because it does not create an opportunity to modify the template.
Recommendation: Use render_template()
.
Code example:
render_template_string("<div>%s</div>" % request.args.get("name"))
References:
1.B. render_template()
with unescaped file extension
Flask only escapes templates with .html
, .htm
, .xml
, or .xhtml
extensions.
This is not always obvious and could create cross-site scripting vulnerabilities.
Recommendation: Only use .html
extensions for templates. If no escaping is needed, review each case and exempt with # nosem
.
Code example:
render_template("unsafe.jinja2")
References:
1.C. Explicitly unescaping variables using Markup()
Markup()
disables HTML escaping for the returned content. This permits
raw HTML to be rendered in a template, which could create a XSS vulnerability.
Recommendation: If needed, review each usage and exempt with # nosem
.
2. Server code: Bypassing the template engine
2.A. Returning directly from a route
Returning values directly from a route bypasses the template rendering engine,
therefore bypassing any escaping. Use functionality provided by Flask to return
content from routes, such as render_template()
or jsonify()
.
Recommendation: Use render_template()
or jsonify()
.
Code example:
@app.route("/index/<msg>")
def index(msg):
return "Hello! " + msg
References:
2.B. Using a Jinja2 environment directly
Flask already comes with a Jinja2 environment ready for use which can be invoked
via the render_template()
function. Using Jinja2 directly may bypass
the escaping protections that are enabled in Flask by default.
Recommendation: Use render_template()
.
Code example:
with open('template', 'r') as fin:
jinja2.Template(fin.read()).render()
References:
3. Templates: Variable explicitly unescaped
3.A. Usage of the | safe
filter
The | safe
filter disables HTML escaping for the provided content. This permits
raw HTML to be rendered in a template, which could create a XSS vulnerability.
Recommendation: Use Markup()
in Python code if necessary.
3.B. Disabling autoescaping with {% autoescape false %}
The {$ autoescape false %}
block disables autoescaping for whole portions of the
template. Disabling autoescaping allows HTML characters to be rendered directly onto
the page which could create XSS vulnerabilities.
Recommendation: Use Markup()
in Python code if necessary.
4. Templates: Variable in dangerous location
4.A. Unquoted variable in HTML attribute
Unquoted template variables rendered into HTML attributes is a potential XSS vector
because an attacker could inject JavaScript handlers which do not require HTML characters.
An example handler might look like: onmouseover=alert(1)
. HTML escaping will not mitigate this.
The variable must be quoted to avoid this.
Recommendation: Always use quotes around HTML attributes.
4.B. Variable in href
attribute
Template variables in a href
value could still accept the javascript:
URI.
This could be a XSS vulnerability. HTML escaping will not prevent this. Use url_for
to generate links.
Recommendation: Use url_for
to generate links.
4.C. Variable in <script>
block
Template variables placed directly into JavaScript or similar are now directly in a code execution context. Normal HTML escaping will not prevent the possibility of code injection because code can be written without HTML characters. This creates the potential for XSS vulnerabilities, or worse.
Recommendation: Use the tojson
filter inside a data attribute and JSON.parse()
in JavaScript.
Code example:
<script>var name = {{ name }};</script>
References:
Mitigations
Item | Name | Semgrep rule | Recommendation |
---|---|---|---|
1.A. | Ban render_template_string() |
python.flask.security.audit.render-template-string.render-template-string | Use render_template() . |
1.B. | Ban unescaped extensions | python.flask.security.unescaped-template-extension.unescaped-template-extension | Only use .html extensions for templates. If no escaping is needed, review each case and exempt with # nosem . |
1.C. | Ban Markup() |
python.flask.security.xss.audit.explicit-unescape-with-markup.explicit-unescape-with-markup | If needed, review each usage and exempt with # nosem . |
2.A. | Ban returning values directly from routes | python.flask.security.audit.directly-returned-format-string.directly-returned-format-string | Use render_template() or jsonify() . |
2.B. | Ban using Jinja2 directly | python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 | Use render_template() . |
3.A. | Ban | safe |
python.flask.security.xss.audit.template-unescaped-with-safe.template-unescaped-with-safe | Use Markup() in Python code if necessary. |
3.B. | Ban {$ autoescape false %} |
python.flask.security.xss.audit.template-autoescape-off.template-autoescape-off | Use Markup() in Python code if necessary. |
4.A. | Flag unquoted HTML attributes with Jinja expressions | python.flask.security.xss.audit.template-unquoted-attribute-var.template-unquoted-attribute-var | Always use quotes around HTML attributes. |
4.B. | Flag template variables in href attributes |
python.flask.security.xss.audit.template-href-var.template-href-var | Use url_for to generate links. |
4.C. | Ban template variables in <script> blocks. |
N/A | Use the tojson filter inside a data attribute and JSON.parse() in JavaScript. |