Beware of Cross-site scripting (XSS) in Django Templates
1. Django Templates support Automatic HTML escaping. 2. Caveates in Django Templates leading to Cross-site scripting (XSS). 2.1 Safe Filter, 2.2 Unquoted Payload, 2.3 autoescape off 2.4 Variable in script tag
Security is critical when developing web application. Cross-site scripting is one of the most common attacks
Never trust user input.
What is Cross site scripting?
An XSS is a client-side vulnerability where the attacker injects malicious client-side scripts into the vulnerable web applications
Purpose of XSS Attack?
- Steal Confidential Information from the User
- Remote Code Execution/Evaluation
Django comes with in-built security features to protect you from XSS attacks.
Django Templates support Automatic HTML escaping where all expressions are HTML-escaped by default.
This feature converts certain HTML characters into their HTML code as follows:
- < is converted to <
- > is converted to >
- ‘ (single quote) is converted to '
- “ (double quote) is converted to "
- & is converted to &
Django will by default render it as
<script src="evil"> as >script src="evil"<
If you’re using Django’s template system, you’re protected except for a few of the cases listed below
Caveats in Django Templates leading to Vulnerable to XSS
Use of Safe Filter
Django provides safe filter with which you can disable escaping for particular data
Syntax: {{ data|safe }}
Lets say data = "<script>alert('1');</script>"
{{ data}} will output <script>alert('1');</script>
{{ data|safe }} will output <script>alert('1');</script>
As data is not escaped, the payload will be executed.
Unquoted Payload
Django’s sanitization does not apply to unquoted data
user.name = {{ username }} ;
If the attacker can inject any JS payload in the name variable, XSS attack would be executed
Turning autoescape off in template context or Block
This tag takes either on
or off
. There are 2 ways to turn autoescape on
or off
.
- In Block, using
{% autoescape off %}
Hello {{ name }}
{% endautoescape %} - In Template context,
response = render(request,"index.html",{"autoescape":False})
Since Autoescape off will disable HTML escaping for that template, any data rendered is vulnerable to XSS.
Variables in href attribute
HTML escaping does not prevent payload execution with javascript:
URI. Variables in href attributes accept javascript:
URI
For use-cases which allow accept links from users, it is vulnerable to XSS
Mitigation: Validate if url does not start with javascript:
url.lower().strip(' \n\t').startswith("javascript:")
Note: make sure condition is
Variables inside script tag
One of the use-cases of Django Templates is to pass data to the <script> tag. If the data is controllable by the user, it can lead to Cross-Site Scripting.
The safe filter is used inside the script tag so all the HTML entities will be escaped
<script>
const data = "{{ data|safe }}";
</script>
Hence a payload like
<script>
const data = "</script><script src="https://example.com/evil.js"></script>";
</script>
could lead to script Injection escalating it to Cross site scripting
The correct way to mitigate it is to use the json_script
> jsonscript -> Returns an object into a JSON object surrounded by <script></script> tags
In the template, we would use it like
{{ data|json_script:"data" }}
For data = "</script><script src="https://example.com/evil.js"></script>"
It would render to
<script id="data" type="application/json">"\u003C/script\u003E\u003Cscript src=\"https://example.com/evil.js\"\u003E\u003C/script\u003E"</script>
But since the type of it is "application/json", the browser won't execute it. At this stage, we have defined the element. The next step would be to get the data from the html element.
{{ data|json_script:"data" }}
<script>
const data = JSON.parse(document.getElementById('data').textContent);
</script>
Reference: https://adamj.eu/tech/2020/02/18/safely-including-data-for-javascript-in-a-django-template/
Please comment below if you are interested in any of blogs related to XSS
- Most critical XSS scenarios
- Types of XSS along with examples
- How to handle input sanitisation for XSS payloads?
- Steps to check if your website is vulnerable to XSS?
- How WAF can help prevent XSS?