XSS prevention for Django
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.
Mitigation summaryโ
In general, always use the template engine provided by Django using render()
. If you need HTML escaping, use mark_safe()
combined with format_html()
and review each individual usage carefully. Once reviewed, mark 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.django-xss
Check your project for these conditions:โ
semgrep --config p/minusworld.django-xss
1. Server code: Marking "safe" content, which does not escape HTMLโ
1.A. Using mark_safe()โ
mark_safe()
marks the returned content as "safe to render." This instructs the template engine to bypass HTML escaping, creating the possibility of a XSS vulnerability.
Example:
mark_safe(html_content)
References:โ
Mitigationโ
Ban mark_safe()
. Alternatively, if needed, use in combination with format_html()
and review each usage carefully. Create an exemption with # nosem
.
Semgrep ruleโ
python.django.security.audit.avoid-mark-safe.avoid-mark-safe1.B. Using the SafeString class directlyโ
The SafeString
class is how Django determines which variables should be escaped and which should not. Elements passed to mark_safe()
are returned as a SafeString
. Invoking SafeString
directly will bypass HTML escaping which could create a XSS vulnerabliity.
Example:
SafeString(f"<div>{request.POST.get('name')}</div>")
References:โ
Mitigationโ
Ban SafeString()
. Alternatively, prefer mark_safe()
if necessary.
1.C. Registering a custom filter with is_safe=Trueโ
Registering a filter with is_safe=True
indicates to Django that the filter absolutely does not introduce any unsafe HTML characters. The value returned from the filter will be marked as "safe" when the input is also marked "safe". Generally, this is acceptable, but if you cannot be certain the filter is safe, it may introduce a XSS vulnerability.
Example:
@register.filter(is_safe=True)
def myfilter(value):
return value
References:โ
Mitigationโ
Do not mark filters with is_safe=True
. Alternatively, prefer mark_safe()
if necessary.
Semgrep ruleโ
python.django.security.audit.xss.filter-with-is-safe
1.D. Use of the html magic method in a classโ
The __html__
magic method is used by the Django template engine to determine whether the object should be escaped. If available, the value returned by the method will not be escaped and could introduce a XSS vulnerability.
Example:
class RawHtml(str):
def __html__(self):
return str(self)
References:โ
Mitigationโ
Ban __html__
in classes. Alternatively, prefer mark_safe()
if necessary.
Semgrep ruleโ
python.django.security.audit.xss.html-magic-method.html-magic-method1.E. Using html_safe()โ
The html_safe()
decorator adds the __html__
magic method to the supplied class. The added __html__
magic method will return the exact string representation of the class (e.g., str(self)
). Because objects with the __html__
method are not escaped, this could create a XSS vulnerability.
Example:
@html_safe
class RawHtml(str):
pass
References:โ
Mitigation:โ
Ban html_safe()
. Alternatively, prefer mark_safe()
if necessary.
Semgrep ruleโ
python.django.security.audit.xss.html-safe.html-safe2. Server code: Bypassing the template engineโ
2.A. Directly writing a response using HttpResponse or similar classesโ
Writing results directly to HttpResponse
or similar classes bypasses the Django template engine. This also bypasses the HTML escaping built into the template engine and creates the possibility of a XSS vulnerability. Use render()
with a template instead.
Example:
return HttpResponse("Hello, " + name)
References:โ
Mitigation:โ
Ban HttpResponse
and similar classes. Alternatively, use render()
.
Semgrep ruleโ
python.django.security.audit.xss.direct-use-of-httpresponse2.B. Globally disabling autoescapeโ
Autoescaping can be globally disabled in Django settings. This should never be done if you are rendering HTML; now, every response returned to the user will need to be audited to ensure it is free of XSS vulnerabilities.
Example:
TEMPLATES = [
{
...,
'OPTIONS': {'autoescape': False}
}
]
References:โ
Mitigation:โ
Ban globally dissabling autoescape. Alternatively, do not globally disable escaping. If HTML escaping is necessary, use mark_safe()
.
Semgrep ruleโ
python.django.security.audit.xss.global-autoescape-off.global-autoescape-off2.C. Setting autoescape=False in a template contextโ
Setting autoescape=False
in a template context will disable HTML escaping for that template. Any data rendered in that template could be a XSS vulnerability.
Example:
response = render(request, "index.html", {"autoescape": False})
References:โ
- Context source code
Template.render()
documentationrender_to_string()
documentationrender()
documentation
Mitigation:โ
description: "Ban autoescape=False
in template contexts"
alternative: "Use mark_safe()
if necessary"
rule: "python.django.security.audit.xss.context-autoescape-off.context-autoescape-off"
3. Templates: unescaped variablesโ
3.A. Use of the | safe filterโ
The | safe
filter marks the content as "safe for rendering." This has the same effect as mark_safe()
in Python code. This will permit direct rendering of HTML and create a possible XSS vulnerability.
Example:
{{ name | safe }}
References:โ
Mitigation:โ
Ban | safe
. Alternatively, use mark_safe()
in Python if necessary.
Semgrep ruleโ
python.flask.security.xss.audit.template-unescaped-with-safe.template-unescaped-with-safe3.B. Use of the | safeseq filterโ
The | safeseq
filter marks the content as "safe for rendering." This has the same effect as mark_safe()
in Python code. This will permit direct rendering of HTML and create a possible XSS vulnerability.
Example:
{{ names | safeseq | join:", " }}
References:โ
Mitigation:โ
"Ban | safeseq
. Alternatively, use mark_safe()
in Python if necessary.
Semgrep ruleโ
python.django.security.audit.xss.template-var-unescaped-with-safeseq.template-var-unescaped-with-safeseq3.C. The {% autoescape off %} blockโ
The {$ autoescape off %}
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 off %}
References:โ
Mitigation:โ
Ban {% autoescape off %}
. Alternatively, use mark_safe()
in Python if necessary.
Semgrep ruleโ
python.django.security.audit.xss.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.django.security.audit.xss.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
- Safely including data for JavaScript in a Django template
json_script
documentation
Example:
<script>var name = {{ name }};</script>
Mitigation:โ
Ban template variables in <script>
blocks. Alternatively, use the json_script
template tag and read the data in JavaScript using the element ID.