XSS prevention for Flask
This is a cross-site scripting (XSS) prevention cheat sheet by Semgrep, Inc. It contains code patterns of potential XSS in an application. Instead of scrutinizing code for exploitable vulnerabilities, the recommendations in this cheat sheet pave a safe road for developers that mitigate the possibility of XSS in your code. By following these recommendations, you can be reasonably sure your code is free of XSS.
Mitigation summary
In general, you should use render_template()
when showing data to users. If you need HTML escaping, use Markup()
and review
each individual usage carefully. Once reviewed, mark the line with # nosem
. Beware of putting data in dangerous locations in
templates. And as always, run a security checker continuously on your code.
Semgrep ruleset for this cheatsheet: https://semgrep.dev/p/minusworld.flask-xss
Check your project using Semgrep
The following command runs an optimized set of rules for your project:
semgrep --config p/default
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.
Example:
render_template_string("<div>%s</div>" % request.args.get("name"))
References
Mitigation
Ban render_template_string()
. Alternatively, use render_template()
.
Semgrep rule
python.flask.security.audit.render-template-string.render-template-string1.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.
Example:
render_template("unsafe.jinja2")
References
- Flask documentation - Escaping behavior
- Bento check - Unescaped Template Extension
- Unescaped template extensions in Flask
Mitigation
Ban unescaped extensions. Alternatively, only use .html
extensions for templates. If no escaping is needed, review each case and exempt with # nosem
.
Semgrep rule
python.flask.security.unescaped-template-extension.unescaped-template-extension1.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.
Example:
flask.Markup(html_content)
References
Mitigation
Ban Markup()
. Alternatively, if needed, review each usage and exempt with # nosem
.
Semgrep rule
python.flask.security.xss.audit.explicit-unescape-with-markup.explicit-unescape-with-markup2. 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()
.
Example:
@app.route("/index/<msg>")
def index(msg):
return "Hello! " + msg
References
Mitigation
Ban returning values directly from routes. Alternatively, use render_template()
or jsonify()
.
Semgrep rule
python.flask.security.audit.directly-returned-format-string.directly-returned-format-string2.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.
Example:
with open('template', 'r') as fin:
jinja2.Template(fin.read()).render()
References
Mitigation
Ban using Jinja2 directly. Alternatively, use render_template()
.
Semgrep rule
python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja23. 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.
Example:
{{ name | safe }}
References
Mitigation
Ban | safe
. Alternatively, use Markup()
in Python code if necessary.
Semgrep rule
python.flask.security.xss.audit.template-unescaped-with-safe.template-unescaped-with-safe3.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.
Example:
{% autoescape false %}
References
Mitigation
Ban {$ autoescape false %}
. Alternatively, use Markup()
in Python code if necessary.
Semgrep rule
python.flask.security.xss.audit.template-autoescape-off.template-autoescape-off4. 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.
Example:
<div class={{ classes }}></div>
References
Mitigation
Flag unquoted HTML attributes with Jinja expressions. Alternatively, always use quotes around HTML attributes.
Semgrep rule
python.flask.security.xss.audit.template-unquoted-attribute-var.template-unquoted-attribute-var4.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.
Example:
<a href="{{ link }}"></a>
References
Mitigation
Flag template variables in href
attributes. Alternatively, use url_for
to generate links.
Semgrep rule
python.flask.security.xss.audit.template-href-var.template-href-var4.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.
References
- Template engines: Why default encoders are not enough
tojson
documentation- How to use
tojson
in a data attribute - Safely including data for JavaScript in a Django template
Example:
<script>var name = {{ name }};</script>
Mitigation
Ban template variables in <script>
blocks. Alternatively, use the tojson
filter inside a data attribute and JSON.parse()
in JavaScript.
Not finding what you need in this doc? Ask questions in our Community Slack group, or see Support for other ways to get help.