XSS in django-allauth <0.63.6
This post details a Cross-Site Scripting (XSS) vulnerability I discovered in django-allauth, a popular Django package for authentication. This vulnerability affected the Facebook provider only, and it was fixed in version 0.63.6 on July 12, 2024.
Background
Before I found this vulnerability, I already reported another one to django-allauth, a login CSRF vulnerability in its SAML provider, which was fixed in version 0.63.3 (maybe I'll write a post about it if people are interested in more posts like this).
At Read the Docs, we use django-allauth for user authentication. I was in charge of integrating SAML into our authentication system, while working on that I noticed the CSRF vulnerability. After reporting it and seeing how quick it was fixed, I decided to do a quick security audit of the project.
The vulnerability
After grepping the codebase for common vulnerable patterns, I found this line of code that caught my attention:
django-allauth allows using Facebook as a provider for social authentication,
and allows using the regular form (oauth2
method) or the Facebook JavaScript SDK (js_sdk
method) to login.
When using the js_sdk
method, the fb_data
variable is passed to the template to be used in the frontend.
Which is then used in the fbconnect.js script to initialize the Facebook SDK.
So, what's the problem here?
The fb_data
variable is marked as safe
(Django won't escape it when including it in a template)
after being transformed into a JSON string.
Since json.dumps
doesn't escape HTML characters,
it's possible to inject arbitrary HTML and JavaScript code into the template.
Exploitation
Using mark_safe
by itself is not a vulnerability,
as long as the content is trusted and doesn't contain user input.
So the next step was to find a way to inject user-controlled content into the fb_data
variable.
As shown below, most of the content in the fb_data
variable is static:
Except for loginOptions
, which includes the value from the scope
query parameter:
With that, we now can inject arbitrary HTML and JavaScript using the scope
query parameter.
But wait... In which page can we control the scope
query parameter?
By following the code, I found that media_js
is used in the providers_media_js
template tag,
which is called in the login_extra.html snippet,
and furthermore that snippet is included anywhere the social providers are listed,
like the login page (/accounts/login/
), and the social account connections page (/accounts/3rdparty/
).
Payload
To exploit this vulnerability, an attacker could inject the following content in the scope
query parameter:
What this does is:
Closes the
script
tag containing thefb_data
variable.Injects a script that shows an alert with the current domain.
Opens a new
script
tag, so the rest of the JSON content is not shown as plain text.
Proof of concept
I created a proof of concept to demonstrate the vulnerability, so you can see it in action, you just need to have Python and uv installed:
It consists of a simple Django project with django-allauth==0.63.5
installed, and a Facebook provider configured using the JavaScript SDK.
$ git clone https://github.com/stsewd/poc-xss-django-allauth $ cd poc-xss-django-allauth $ uv run manage.py migrate # Create a user to log into the application. $ uv run manage.py createsuperuser $ uv run manage.py runserver
- XSS in login page:
While logged out, go to
http://127.0.0.1:8000/accounts/login/?scope=</script><script>alert(document.domain)</script><script>
.
- XSS in social connections page:
Go to
http://127.0.0.1:8000/accounts/login/
.Log in with the user you created.
Go to
http://127.0.0.1:8000/accounts/3rdparty/?scope=</script><script>alert(document.domain)</script><script>
.
Showing an alert is just a simple example, but an attacker can execute any JavaScript code in the context of the user's session.
Mitigation
You should never mark user-controlled content as safe, but if you find yourself wanting to include JSON content in a template, escaping will break the JSON format.
Luckily, Django has a built-in template filter to include JSON content in a template safely, json_script. Sadly, that filter wasn't available at the moment the allauth code was written, but it's been available since Django 2.1, since allauth supports newer versions of Django, it was possible to use it, as you can see in the fix.
Timeline
11/07/2024: Found and reported the vulnerability to django-allauth.
12/07/2024: Maintainer confirmed the vulnerability and released version 0.63.6 with the fix.
Acknowledgements
I'm always surprised by how quickly open source maintainers fix security vulnerabilities (so much faster than commercial software vendors), kudos to Raymond Penners, maintainer of django-allauth.
It's also great I have the support at Read the Docs to spend part of my work time on security audits on packages we use. Even if the vulnerabilities don't affect our systems directly (we don't use the Facebook provider), it's nice to have the chance to give back to the community.
Are you still using django-allauth <0.63.6
? The fix was released more than 6 months ago, please update your dependencies!
Thank you for reading, and let me know if you'd like to see more posts like this!
Comments
Comments powered by Disqus