Hacking Django websites with Cross Site Request Forgery
Cross Site Request Forgery is when a malicious site can cause a visitor’s browser to make a request to your server that causes a change on your server.
If one of your logged in users accesses a malicious website containing Cross Site Forgery Request code then your website can be fooled into thinking a request come from that site is actually coming from that user. That may look like this:
- The user accesses the malicious website
- The website executes JavaScript to perform a request to your website
- The browser exposes the user’s cookies to your website
- Your website thinks the request came from them
Here’s a concrete example of a CSRF attack:
And without need for interaction from the user:
The attack is rooted in the fact that during requests to your website, your browser will voluntarily include cookies in that request that were created by your website in previous requests. Cookies such as session cookies which are used to determine which user is logged in.
The impact of this attack depends on what the forms on your website do. Perhaps the form fixes Python and Django bugs from your GitHub repository (like on CodeReview.doctor) or maybe it transfers money to someone? Bids on an item? Transfers ownership of the account to someone else? The possibilities are endless.
Code
The simplest proof of concept React code is as follows:
import React from 'react';
export default function (props) {
return (
<form action={"http://codereview.doctor/higher-tier/django-doctor-example/"} method="POST">
<input type="submit" value="Do something innocent" />
</form>
);
}
And to make the form submit automatically with no user interaction required:
import React from 'react';
export default function (props) {
React.useEffect(function() {
document.forms[0].submit()
}, []);
return (
<form action={"http://codereview.doctor/higher-tier/django-doctor-example/"} method="POST">
<input type="submit" value="Do something innocent" />
</form>
);
}
Prevention
Follow this advice to prevent CSRF attacks in your Django website:
And use Django’s csrf_token template tag in your forms. This will result in Django verifying the submitted form came from your website (the expectation being only your website will have valid csrf tokens).
This protection can be trusted as long as the CSRF cookie is not vulnerable to being stolen. If your website is vulnerable to packet-sniffing attacks then the CSRF cookie can be stolen and used later in the CSRF attack.
CSRF and packet-sniffing
Your website is vulnerable to CSRF attacks even with CSRF protection is enabled if Django’s CSRF_COOKIE_SECURE
setting is not set CSRF_COOKIE_SECURE = True
, makes the browser only send cookie over secure HTTPS connection. The setting is disabled by default because it will prevent forms from working under HTTP (such as when working in local development environment).
Cookies sent over insecure HTTP are not encrypted, so hackers can steal the CSRF cookie using a packet sniffer — allowing them to use it to trick the browser into thinking a request on their website was performed on your website by the logged a user.
Some might argue that CSRF_COOKIE_SECURE = True
is not needed if the production website’s server config redirects http to https. However, configuration mistakes can occur. A developer might think http to https redirection is in place but it might not work as expected. Security of the Django app should not rely on Django trusting some other system does the hard work for it. Sure, redirect http to https via server config but also set CSRF_COOKIE_SECURE = True
. Server applications should not trust each other just because they were made by the same developer or team.
This feature should not necessarily be hard-coded True
as otherwise running the website under local development would be more difficult, so it’s a good idea to feature flag this:
literal_eval is that helpful function you always needed but never knew was provided out of the box by Python. It can convert string “False” to boolean False and string “True” to boolean True. Very useful for coercing environment variables to booleans (environment variables are always strings).
Preventing human error
It’s possible this mistake can be missed during code review. Code Review Doctor is a GitHub code review bot that will point these issues out during code review so you don’t miss them. You can even check your codebase is already afflicted here.