Django Doctor audits code and auto fixes Django anti-patterns. We checked 666 Django projects for insecure settings.py configurations and were shocked that 20% of the Django websites had these vulnerabilities:
- 8% had not activated SecurityMiddleware read more
- 6% were vulnerable to clickjacking read more
- 5% were vulnerable to CSRF attack read more
Django docs suggests SecurityMiddleware is placed near the top of your
MIDDLEWARE settings for good reason: it performs a suite of security checks and enhancements that would otherwise leave your website to the mercy of some simple hacks.
How would you solve these security flaws? Try our Django security challenge.
Clickjacking is an attack where one of your logged-in user visits a malicious website, and that website tricks the user into interacting with your website via an invisible iframe (which in the demo I made mostly transparent otherwise you would see nothing interesting):
A malicious website that is accessed by one of your logged in users can fool your website into thinking a request come from that user. That may look like this:
- The user accesses the malicious 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:
X-Content-Type-Options header to
This header indicate to the browser that the when the MIME types advertised in the Content-Type headers is not present, the browser should not guess it by "sniffing" what the content "smells" like. The browser uses the MIME type to determine to render HTML, execute JS, etc. Sniffing may be a very helpful feature for when servers are misconfigured and do not expose the
Content-Type header. But it can be abused, as the following simple proof of concept shows:
# settings.py MIDDLEWARE = [ # "django.middleware.security.SecurityMiddleware", "django.middleware.common.CommonMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", ... ] SECURE_CONTENT_TYPE_NOSNIFF = False # pre-Django 3.0 it was False by default. Post 3.0 it's True # views.py class HomePage(View): def get(self, *args, **kwargs): # simulate a misconfgured server that returns a response that has no content type response = HttpResponse("<script>alert('hello world')</script>") del response['Content-Type'] return response
The outcome when navigating to
HomePage shows the "hack" worked:
Such attacks can be avoided if the
SecurityMiddleware is present and the
nosniff feature is active:
# settings.py MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.middleware.common.CommonMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", ... ] SECURE_CONTENT_TYPE_NOSNIFF = True
Then the browser does not fall for the trick:
Would you be happy if every hyperlink you clicked on told the target what website you came from? Aside from the privacy aspect, this leaks information to potential bad actors (e.g, if the url contained private information).
SecurityMiddleware sets the referer policy header based on
SECURE_REFERRER_POLICY, which impacts user privacy and it leaks information for bad actors to abuse.
Or try out Django security challenge.