XSS in Django Admin

AS web developer my every day tasks involve working with Django which I like since it’s quite secure framework (compare with CVE list). Nevertheless as some say: “shit happens” and vulnerabilities are found. Some time ago a problem in admin panel that allowed for XSS attacks was discovered.

Example application

In order to give you an idea of how this change can affect a project I’ve created a sample project you can download from my gitlab. Go through readme to set it up. It’s basically an interface for some place where you can add authors and books with limited information.

Main page for book listing

Main page lists already added books. In order to add book, first click “Add new author”:

Creating author form

Here you can define first name, last name and personal page. This needs to be a valid URL. As you might be wondering from the description of the vulnerability this URL could be troublesome. And it is. A little bit. Standard tricks with entering <script>alert()</script> will not work since this is validated and needs to be a correct URL.

How do URLs work?

OK, so let’s dive a little bit into the structure of a URL. URL is something that can consist of many parts: scheme, authentication credentials (yes, they can be included there, however, not everyone will honor them), fragments, query parameters and so on.

One interesting part is scheme. Typically we use http:// or https:// scheme. More advanced users are aware of schemes like ftp:// or pseudoschemes like file://. The one that is interesting to us is javascript: that allows us define javascript code that will be run by browser when we click on the link or enter it in search bar. Or at least it will try to run it unless it’s been set not to do so. Below my Safari telling me (in Polish) that it won’t run code from intelligent search bar.

The good news is that this code will be run when passed as clickable link.

Back to the app

OK, so we know that the link we would like to pass will have a structure of javascript:someJavaScriptCode('With some parameters maybe'). The sad part here is that Django will still refuse to accept it as valid URL. The only way to insert this malicious code here would be to pass it through some untrusted sources (maybe more applications could be touching the database), migrating data, or writing custom form without proper validation. As far as I know, with the default configs it’s impossible. I will add the malicious entry by hand:

from bookshop.models import Author
Author.objects.create(first_name='Adam', last_name='Adamski', personal_page='javascript:alert(document.cookie)'

And now let’s log into the admin panel which is under localhost:8000/admin.

Victim’s perspective

So the victim admin logs into their panel, clicks on author details and sees this:

I must say that this does not look like something I would immediately want to click, but, well, users are quite creative in trying to do themselves harm, so why not. As we can see the url that was previously not validated positively appears as clickable link in admin panel.

When it’s clicked, an alert pops up showing all cookies JS has access to.

This will not happen when we change Django version from 2.2.1 to 2.2.2 in requirements.txt file. This link would not be displayed at all.

Summary

Maybe this vulnerability is far from being spectacular, but nonetheless proves that we have to be careful, since even default validation mechanisms in forms have declined to accept this value, it was displayed in admin panel without undergoing any validation. It’s not non-clickable exploit, requires user interaction, and is not very convincing, but it exists and may be abused.