Closing session
Published June 13, 2025
This video features Ashley Mathew and Mario de la Ossa at DjangoCon Europe 2023 in Edinburgh, Scotland.
A Beginners Guide to Security Exploits in Action
by Ashley Mathew & Mario de la Ossa
https://pretalx.com/djangocon-europe-2023/talk/9N97WM/
It’s one thing to read the Django security page and follow the recommendations. It’s something completely different to actually understand why those recommendations exist.
The talk will cover 5 different security vulnerabilities (spending ~5 mins on each) that are baked into a fake MySpace clone:
Each step will introduce in detail how to exploit the vulnerability, followed by patching and validation.
Mario de la Ossa uses the deliberately insecure TeamSpace Django application to demonstrate five vulnerabilities: SQL injection, unsafe HTML rendering and XSS, wildcard `ALLOWED_HOSTS` and CSRF-related host handling, open redirects, and an exposed `SECRET_KEY`. He shows how each exploit works, including deleting data, running JavaScript, manipulating password-reset links, phishing credentials through redirects, and forging password-reset tokens or JWTs. The fixes include using Django’s ORM or parameterized raw queries, sanitizing user HTML with an allowlist, restricting hosts, validating redirect destinations, rotating credentials, and keeping secrets out of source code.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Welcome to the Hi everybody, good morning. Thank you very much for being here. Uh before we get started, I wanted to call out that my coworker Ashley Matthew was originally meant to give this presentation, but Unfortunately, she had a last-minute emergency and wasn't able to join us today. She still prepared this wonderful application and presentation that I hope you're gonna enjoy. Uh I also want to point out that over the past ten years my main framework has actually been Ruby and Rails. So I want to apologize beforehand for any sort of Django mistakes I might make. Um but that's also why this application is exploitable, right? Um so a little bit about me. I currently live in Managua, Nicaragua, Central America, and I'm a back-end
engineer at Apptable. Like I said, been working on Rails for the last 10 years. Scalability is my passion And understanding security issues has served me very well in my career, which is why uh we wanted to share this information with you today. Um so I found that the best way to understand security for myself is to actually work through the exploits Uh and while frameworks do keep you out of trouble most of the time, all of these security exploits we'll see today can happen in any framework. It really just takes a second of laziness or carelessness. Uh we're gonna be talking about five specific vulnerabilities here. Uh SQL injection, bad HTML standardization, uh misconfiguration of your allowed hosts, open redirects, and a leaked secret key. By the way, uh we do have uh a repo available.
Uh well you'll be able to see all of the code. And we actually also have open PRs that showcase each of these vulnerabilities and their fix. So I would like to introduce you to Team Space, the least secure social media platform ever. Uh we built Teamspace as both a fun platform for a team to mess around with and a playground for exploiting security vulnerabilities. Uh you'll see it's a little bit like MySpace where you can do some very interesting customizations to your page, uh leave comments for people, uh and uh you know add uh custom JavaScript, custom CSS, just uh We let you do whatever you want to your page, even inject random JavaScript libraries. Uh so to kick things off. I want to start with something that most people have likely heard of, but maybe haven't had a chance to see in action, which is SQL injection.
Now, your typical Django's day-to-day ORM usage handles this out of the box. So you only really have to think about SQL injection when you start using non-standard things, like the raw query sets. However, if you do look at the docs for raw query sets, they're extremely loud about the possibility of SQL injection. For Teamspace, we wanted to add search so that you could find that cool post that you only remember a part of. We optionally apply search terms when they're present, which at the time we were writing TeamSpace, I really couldn't remember how to do. So instead we just manually built the query string using string formatting. And we execute it as a raw query, which opens us up to SQL injection. So here you can see how the search works, and here is how we ended up implementing it.
Just a raw string and we inject Uh user-provided input. So the way uh SQL injection works is you're literally injecting your own input into a SQL query. So this particular payload that I'm going to use will have three sections. Section one is just me closing the original uh SQL command. So you can see I'll uh just close the string, close the uh semicolon, and go on. And then I can do whatever I want. In this case we're gonna do two more things. Uh first we're gonna delete a post. And second, we are going to try to gain access to something that we maybe shouldn't have access to. Uh like for example, you could assume
uh we are filtering by your friends and maybe you can get data from people that are not your friends, posts and things that look like. At the bottom you can see what the server is going to see after I uh use this command on the website. And let's go ahead and check out how this works. First we need something to be delete. So let's assume that I accidentally sent tin my password and I did this publicly. Not a very good idea. And oh no, I've noticed, but I also don't have a way to delete this. But I'm a hacker, so let's go ahead and hack into this. So I'm gonna click reply here. Uh you won't be able to see this, but I'm just grabbing the
post ID, which is gonna be in the URL bar. And then I'm going to switch over to the search uh area and inject my payload. So I'll close the original uh SQL command Which I can see because this is a public repo, but even if you couldn't, there are ways to round it. Uh then I will um have a delete command for my post, where I ID 11 in this case And then for some extra fun, I'm going to go ahead and select some uh posts. Although I know that the word secret doesn't show up in any post right now, so I'll go ahead and switch that to something that will actually return some data.
So let's use the word my. And when we search, we see that we actually get uh results, but that's not very exciting, right? We could also we could already get results. The important thing is that when I refresh this, the post is gone. So there are plenty of possible fixes for this vulnerability. In the PR that I opened against Teamspace, we did what we should have done all along, which is to conditionally build a query using the built-in filter. Which as mentioned before will correctly handle SQL injection. You can see there where we started using uh filter depending on uh input. And there is another way to fix this as well. As the Django Docs recommend, if you're gonna use uh
raw, you should pass user input as an array. And this approach will also handle SQL injection for you. So next I'm gonna move on to something else many people have probably heard of and encountered, which is HTML standardization, right? How do you deal with user input? That might be a little Not safe. Uh now I don't know if anybody else here has had the opportunity to design your own MySpace page, but if you didn't, let me assure you that you are missing out. With Team Space, we wanted to bring back some of that over-the-top fun. And we did so unwisely by allowing all user content to be displayed without any sort of uh sanitization, no HTML escape. And
we did this by using the safe keyword on the template. Now by default, Django escapes variables included in the template unless they're marked as safe in some way, including using this uh template tag. Uh so now that means that we can share images and make customizations using JavaScript, but that comes with some downsides as we'll see. So I can go ahead and leave tin a comment and I can actually use JavaScript in this comment. So all that on alert Which will show up every single time somebody looks at that comment, which is going to be extremely annoying. So everybody that visits
Tin 'Page will now get that alert every time. So that's annoying, but it's pretty trivial to actually take this from annoying to outright malicious. For example, I could capture user's cookies. So we still have the alert, but also uh we can print the cookie down to the console, or you know, we could do something a little bit more interesting with that if we had some more time. Um now to fix this, we could take all the fun away and remove the safe tag, but there's actually a hint nestled in the Django Docs for something completely different, which is to use the bleach library. With the Bleach library, we can set an allow list of HTML that remains unescaped while ensuring the rest of HTML is safe.
Now bleach is not completely safe, uh especially if you don't know vulnerabilities that can be introduced via various HTML tags. Like Canvas is a pretty good one. Uh but using it allows us to keep some of the fun, like for example, uh links and images, while dropping anything that's annoying or outright dangerous, like JavaScript. Another approach could be to use uh some sort of uh limited intermediate language like markdown, uh but that can come with its own security pitfalls as well. So next, we're gonna go on to my two favorite vulnerabilities on this list. And I like them because of how much creativity they inspire when exploiting. Uh these vulnerabilities are gonna leverage the fact that I set my allowed host to a wildcard.
For the first vulnerability, it is also going to help that I've done the same with the CSRF token. So the first one is allowed hosts. Allowed hosts is meant to be a list of domains that Django is safe to serve traffic from So for Team Space, that really should just be localhost for local development and our production URL. But when we got started, we used a wildcard because we didn't really know what the production domain was going to be. But as we're going to see, that was not a good idea. So this is the payload that I'm going to use here. In this vulnerability, the first command I'm running is going to get a cookie from our server just to get a CRFR CSRF token. But you can see that the host I'm using is aptable-hacker. com. So after getting that token, I'm gonna go ahead and use the last
command to create a password reset for a random user in TeamSpace. However, instead of using the actual host, I'm again gonna use the hacker domain, which will send them over to a domain that I that the hacker controls. So let's see that in action here. Um just getting a C R for C S R F token here. We get a reply. And then I just hit the password reset endpoint. And by the way, the Gmail on the right is uh teamspace tmp at gmail.
com, just to show that we're actually gonna get an email from Teamspace itself. Uh you can check the domain, teamspace at aptible. com. However, uh the uh the email domain the domain inside of the email is actually aptable -hacker. com. This happens because Django is using the host provided by the host header when building our URLs in templates. Uh you can imagine why this is extremely bad. So before I fix allowed host, I want to share another vulnerability that's exploitable because of the combination of not setting the allowed host and not properly handling redirects. Um so TeamSpace has an open redirect. And an open redirect is a redirect that doesn't validate where the destination is.
They just send you there. So for example, uh let's say that one of my team members sent me this link. They're trying to show me something, and I'm probably gonna click it without question because they're a team member. However, this is what the URL actually looks like. Uh sorry for the numbers in the domains, but you can see that uh the left and the right domain are different. So let's see what happens if I end up clicking on this link. Uh it sends me over to Teamspace, so I'll just go ahead and log in without question. I want to see what they sent me after all. But oh I'm sorry, I typed my password wrong. So let me just go ahead and do that again real quick Oh no, my password got stolen. So this works because of the next parameter in the link that I was sent.
On a successful login , Uh you want to send people over to some sort of landing page, but in this case, we're sending them to another website that's mimicking team space. And this happens because her code is blindly redirecting without doing any validation. So we should go from this over to this. We should use URL has allowed host and scheme. And this will ensure that any redirect only goes to approved domains. In this case, what I set to in the allowed hosts and optionally I could also make it work with just HTTP as locations. And finally, for our last exploit, I wanted to show you off how we might exploit an exposed or generic secret key.
Now this example is going to be a little bit contrived, but I promise that I'll highlight a non-contrived uh exploit at the end. So in the code for team space, we accidentally hard-coded the secret key to this actual string secret key. And since this repository is public, anybody can see that I did that. So let's assume that I did the same thing with a read-only set of database credentials. Like I mentioned, contrived. But accidents do happen. So the default Django implementation for a password reset token relies on a few things: the user's primary key, the password, the login timestamp, the email, and finally the actual secret key. So with those two uh things I have, I now have access uh to all of that data that I need.
So I can write this script uh where I get the user's primary key, the password, uh the hash password, by the way, not the real password. Uh The login timestamp, the email, everything. And I can then generate a valid password reset token for any user. So this would give me access to any users. Team space account. So like I said, this is a little bit contrived, and uh we can see um the script in action here, and we end up printing a password reset URL at the bottom and Right now you'll have to take my word that it works, but it does work. Um now Django does generally do a good job of being defensive about how the secret key is used.
In most places you're also going to need a second piece of information. Often database access, like in this case, to meaningfully exploit it. Uh however, not all frameworks are as nice as Django. I know of at least one application leveraging Flask that hit this exact issue. So even with Django, if you step outside of the core, there are plenty of libraries that will leverage the secret key as a means of ensuring something For example, the very first JWT library for Django that I came across uses the secret key as a sign-in key by default. Given that JWTs are frequently used for securely communicating sensitive information, including authentication between microservices. This would be pretty bad if my key was exposed. Because some that would mean that somebody could force JWTs without me ever finding out
Now the fix for this, thankfully, is pretty simple. Uh we rotate our secret key on our database password, and we stop hard-coding our secret key on our code. Simple, but it's also a nice reminder that you need to understand how all your authentication is implemented And that's it. I covered a lot of ground in not a lot of time. Hopefully you learned something new. And if not I at least gave you something to think about Like I mentioned earlier, you can find uh all the code I use here uh on our repo, and you'll find open PRs that showcase each of these five security exploits, as well as their fixes. So I want to thank you very much for your time. I hope this talk was very useful to you. And if you have questions or you want to come discuss the merits of Rails versus Django, uh
you can pass by the Apptable booth. I'll be there all day. And we'll also have the Steampace, the TeamSpace app available for you to play around with, and we'll have demos on how to use Apptable to deploy your apps. So thank you.
It happens when user input is inserted directly into a raw SQL string, allowing an attacker to close the original query and add commands such as deleting data or retrieving records they should not see.
Discussed at 3:12Prefer Django’s built-in `filter()` methods, which safely construct the query. If raw SQL is necessary, pass user input separately as query parameters rather than interpolating it into the SQL string.
Discussed at 5:30Marking user content as safe disables Django’s normal escaping, so a user can store JavaScript that runs whenever another person views the content—for example, displaying an alert or accessing cookies.
Discussed at 7:02Use an allowlist-based sanitizer such as Bleach to preserve permitted elements like links and images while removing dangerous HTML and JavaScript. Markdown is another option, but it also needs careful security review.
Discussed at 7:48A wildcard lets attackers supply their own host name, which Django may then use when generating URLs. In the demonstration, this caused a password-reset email to contain a link pointing to an attacker-controlled domain.
Discussed at 9:19If a login endpoint blindly trusts a destination parameter, an attacker can send a convincing link that redirects the user to a fake site after login, where the user may enter their password again.
Discussed at 11:40Django password-reset tokens depend on the secret key along with user data, so an attacker who obtains the key and the required account information can generate valid reset links for users. Other libraries, such as JWT implementations, may also use the key to sign authentication tokens.
Discussed at 13:12Rotate the secret key and database credentials, and stop hard-coding the secret key in source code. You should also understand how the application and its authentication libraries use the key.
Discussed at 15:35Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025