Hacking Django websites
Tags: django, webdev, python, security
Published: 2020-12-10 13:37:33

Django docs suggests [SecurityMiddleware](https://docs.djangoproject.com/en/3.1/ref/middleware/#module-django.middleware.security) 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.

_Ready for a Django security challenge? Play our [Django security challenge](https://django.doctor/challenge/settings-security)._

### X-Content-Type-Options

`SecurityMiddleware` sets the `X-Content-Type-Options` header to `nosniff` to prevent hackers from tricking your website into executing a malicious javascript file that they uploaded.

This header indicate to the browser that the MIME types advertised in the Content-Type headers should not be changed (by "sniffing" the content). The sniffing feature is the browser being helpful when a developer or server misconfiguration misidentified the `Content-Type`. If the browser respected an incorrect MIME type then a javascript, css, or image file would not work and the website would break. Very helpful feature. But it can be abused, as the following simple proof of concept shows:

```py

# 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:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/o9anfdz0cp3znsbihcvc.png)

This simple proof of concept simulated the following more complex situation:
- A bad actor uploaded HTML containing javascript (maybe pretending to be an image file).
- The file was served by your website but does not set MIME type in the Content-Type header
- The browser inferred the MIME type based on the content
- The browser executed the javascript

This would be avoided if we the `SecurityMiddleware` is present and the `nosniff` feature is active:

```py
# 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:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yv542dgedp18d6w3bnjw.png)


### X-XSS-Protection
`SecurityMiddleware` sets the `X-XSS-Protection` header to `1; mode=block` when `SECURE_BROWSER_XSS_FILTER` is `True` to enable the browser's built-in XSS protection.

This XSS protection has been mostly superseded by [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). Older browsers still support this feature, but many browsers have removed this feature. Users with older browsers are still important to cater for because security of your website cannot assume everyone is running the latest version of Chrome.

Without the `SecurityMiddleware` and `SECURE_BROWSER_XSS_FILTER` then the website will not receive the benefit of being protected against the [many possible XSS attacks](https://pentest-tools.com/blog/xss-attacks-practical-scenarios/).


### Referrer Policy

`SecurityMiddleware` sets the referer policy header based on `SECURE_REFERRER_POLICY`, which [impacts user privacy](https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns) and is an attack vector for bad actors that aim to dupe users into thinking they are still on your website.

The referer header can be abused by the target website: maybe secrets in the your website URL is leaked. More convolutedly, maybe a bad actor can add a link to their website. The target can then read the referer header and style their website to look like your website. A user not paying attention may think they're still on your website because:
 - your website linked to it
 - it looks like your website

So the user may then input credentials and personal details in the bad actor's website.

This can be prevented by not exposing the referer header by settings `SECURE_REFERRER_POLICY`, which is then handled by the `SecurityMiddleware`.

### Clickjacking

See [Hacking Django websites part 2: clickjacking](https://dev.to/djangodoctor/django-hacking-clickjacking-4hg2)

### Cross Site Request Forgery

See [Hacking Django websites part 3: CSRF](https://dev.to/djangodoctor/hacking-django-websites-cross-site-request-protection-2i1i)

### SSL Redirect

SecurityMiddleware can redirect HTTP connections to HTTPS if `SECURE_SSL_REDIRECT` is set to `True`.

If you do not redirect HTTP to HTTPS then passwords and personal information will be transported over plaintext, and a [Man In The Middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) could read them.

See [Hacking Django websites part 4: Man In The Middle](https://dev.to/djangodoctor/hacking-django-websites-man-in-the-middle-attack-3l4)

### Does your website have security vulnerabilities?

Over time it's easy for security vulnerabilities and tech debt to slip into your codebase. I can check that for you at [django.doctor](https://django.doctor), or can [review your GitHub PRs](https://github.com/marketplace/django-doctor/):

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d10epulva9d373jgldhu.png)

Or try out [Django refactor challenges](https://django.doctor/challenge).