XSS Prevention for Flask

Semgrep ruleset for this cheatsheet: https://semgrep.dev/p/minusworld.flask-xss

This is a cross-site scripting (XSS) prevention cheat sheet by r2c. 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 mitigates the possibility of XSS in your code. By following these recommendations, you can be reasonably sure your code is free of XSS.

Exploitation Conditions:

Check your project for these conditions:

$ semgrep --config https://semgrep.dev/p/minusworld.flask-xss

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.

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.

Code example:

flask.Markup(html_content)

References:

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.

Code example:

{{ name | safe }}

References:

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.

Code example:

{% autoescape false %}

References:

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.

Code example:

<div class={{ classes }}></div>

References:

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.

Code example:

<a href="{{ link }}"></a>

References:

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.

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.
  • Docs »
  • Security Cheat Sheets »
  • Flask XSS

« Previous Next »