Template XSS Protection Disabled
ID |
python.template_xss_protection_disabled |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Misconfiguration |
Language |
Python |
Tags |
ASVS50:v1.3.1, CWE:80, NIST.SP.800-53, OWASP:2025:A02, OWASP:2025:A05, PCI-DSS:6.5.7 |
Description
Improper configurations or misuse of Python’s templating system can disable XSS protection, exposing applications to cross-site scripting attacks.
Rationale
Cross-Site Scripting (XSS) is a vulnerability that occurs when an application includes untrusted data in a web page without proper validation or escaping.
Python’s templating system is designed with built-in protections to prevent cross-site scripting (XSS) vulnerabilities. These protections automatically escape HTML input, ensuring that user-supplied content is not executed as code on the client side.
However, developers may inadvertently disable these protections by marking content as "safe" or bypassing the template engine, increasing the risk of XSS attacks. Some common misconfigurations and practices can lead to such vulnerabilities:
Template Variables In Dangerous Locations
Unquoted Variable In HTML Attribute
When an HTML attribute value is unquoted, it can lead to parsing issues or security vulnerabilities such as injection attacks. Quoting attribute values is a best practice to ensure integrity and security.
See the following template example:
<input type=text name=username value={{ username }}>
If username includes spaces or special characters, it may break the HTML structure or lead to unexpected behavior.
Variable In Href Attribute
When using template variables in href attributes, there’s a risk of allowing JavaScript URIs, which can lead to Cross-Site Scripting (XSS) vulnerabilities. HTML escaping doesn’t mitigate this risk because it doesn’t validate the URI scheme itself.
See the following template example:
<a href="{{ user_input }}">Click here</a>
If user_input contains "javascript:alert('XSS')" it could execute a script.
Because a javascript: URI is the whole of the residual risk, the variable is only reported when it can
supply the URL’s scheme — that is, when it opens the attribute value. A variable that lands further
in, after a {% url %} tag, a literal path, a ? or a #, cannot introduce one: the scheme is already
decided, and the engine’s own escaping turns " into " so the value cannot break out of the
attribute either.
<a href="{{ entry.link }}">Feed item</a> <!-- reported -->
<a href="{% url 'core:list' %}?id={{ value }}">Filtered</a> <!-- not reported -->
<a href="/static/docs/{{ page }}">Docs</a> <!-- not reported -->
Even in scheme position, an expression the application generates cannot carry one, so it is not
reported: get_absolute_url (Django’s convention for a model’s canonical path, a reverse()`d route),
`request.path, and a primary key or _id, which is an integer.
<a href="{{ object.get_absolute_url }}">Object</a> <!-- not reported -->
<a href="{{ request.path }}">Reload</a> <!-- not reported -->
Variable In Script Block
When placing template variables directly into a JavaScript block, there’s a risk of code injection, which can lead to XSS vulnerabilities. HTML escaping doesn’t protect against this risk because JavaScript can execute without relying on special HTML characters.
<script>
var userInput = {{ user_input }};
</script>
If user_input contains malicious code like alert('XSS');, it can execute in the context of your page.
Template Unescaped Variables
| These issues are reported with a lower severity degree. |
Safe and Safeseq Filters
These filters mark a string or sequence as safe, preventing it from being auto-escaped in a Django template.
<p>{{ user_inputs|safeseq|join:", " }}</p>
A plain {{ value|safe }} in a template file is not reported when every value the views render it with is
safe as markup: the author’s text, or configuration the rule trusts (see Configuration).
Values the application’s models and configuration hold
A variable is also settled by what holds its value. On a model instance a view renders — X.objects.get(…),
X.objects.filter(…).first(), get_object_or_404(X, …), an element of X.objects.filter(…) — a field
the model declares a number, a date or a UUID, a validated slug, and the url of a FileField are not
reported in an href. A read of the settings or of the configuration administrators edit is safe in an
href and under |safe. Configuration is told by where the module imports it from, never by a name alone: the settings module
(from django.conf import settings); the configuration libraries administrators edit — config from
django-constance, global_preferences_registry.manager() from django-dynamic-preferences,
get_solo() on a django-solo SingletonModel, for_request()/for_site()/load() on a Wagtail
BaseSiteSetting or BaseGenericSetting; a function of the project every return of which is one of
those reads; and the accessors the trusted_config_accessors property lists. A project object or function
that merely has a configuration-like name (config, get_config()) is not configuration. A value fetched from a remote feed or catalogue is still
reported:
def support(request):
entry = requests.get(settings.CATALOGUE_URL).json()
return render(request, 'support.html', {
'support_url': settings.SUPPORT_URL, # not reported (trust_settings)
'banner': config.BANNER_TOP, # not reported under |safe (constance, trust_admin_config)
'homepage': entry['homepage'], # reported
})
Autoescape Off Block
Using the {% autoescape off %} block within your templates disables the automatic HTML escaping feature, allowing content to be rendered as raw HTML. This can be useful for certain scenarios but poses significant security risks if not handled carefully.
{% autoescape off %}
{{ user_input }}
{% endautoescape %}
In this example, any HTML in user_input would be rendered directly without escaping.
Configuration
| Property | Default | Meaning |
|---|---|---|
|
|
A value read from the Django settings module is safe in an |
|
|
A value read from the configuration administrators edit in the database ( |
|
|
Fully qualified names of further functions or objects that return configuration, such as |
Remediation
Carefully review every issue addressed by this detector to ensure that the unescaped HTML code is safe.
To effectively address XSS vulnerabilities when using Python templates enable all the autoescape settings.
References
-
CWE-80 : Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS).