Django HTTP Strict
Transport Security Guide: What
It Is and How to Enable It

stackhawk

StackHawk|September 29, 2021

In this post, we will learn how to enable the Django http strict transport security, which forces the browser to load your site over HTTPS.

Let's say you've scanned your website with a web security tool or received an email from a do-gooder who says you're missing the HSTS header. At first, this may seem daunting, but it's technically simple to resolve. All you need is to properly serve TLS traffic over HTTPS and then add a couple of headers to your website. And now you may be wondering what HSTS is and how you can add these headers in Django. Luckily, you landed here—because that's exactly what this post is about. We'll start with a quick overview of HSTS. After that, we'll dive into how you can enable it for a Django app.

Find and Fix Application Security Vulnerabilities with Automated Testing

What Is HSTS?

If you're into reading IETF specifications, you can check out the HSTS spec for yourself. They published it in 2012.

Members from PayPal, Google, and others designed HSTS as a mechanism for web servers to direct user agents to use HTTPS only.

With HSTS enabled, users cannot bypass any issues with the certificate either.

Here's a spoiler alert for you! The real reason for doing this is to protect cookies—especially session cookies—from being hijacked when and if the site is first loaded over HTTP. It does this by telling browsers (user agents or UAs) to immediately load the site using HTTPS even if the user only puts in the "http:" URI scheme rather than "https:". Even if you redirect users from http to https, the initial hit is over plain text and the cookies can be seen by attackers.

An HSTS header is relatively simple. It looks like this:

Strict-Transport-Security : max-age=3600 ; includeSubDomains

The user agent will cache the HSTS policy for your domain for max-age seconds. When the user visits your site, the browser will check for an HSTS policy. If it finds it, then boom! It uses HTTPS even if you went to http://example.com instead of https://example.com. Now, we're going to focus on enabling HSTS in a Django app.

How Do You Enable HSTS in a Django App?

There are actually two main ways to enable HSTS headers in a Django app. First of all, you can add the headers via your server, reverse proxy, or gateway. With this approach, you don't have to change any code or add middleware. In fact, I'd personally go this route first, especially since you probably need to update your TLS certs from time to time (TLS certs and HSTS are very closely related). Although this is my own preference, you and your organization might prefer other options. For example, let's say you have access only to the Django app code and therefore need a way to do it in Django only. This is not a problem! You can use Django to add the HSTS headers too.

Use Django to Add Headers

Django has built-in middleware to handle adding the HSTS headers to all responses. All it takes is to add the settings to your server. The two main HSTS settings (shown below) have been there since version 1.8 (released in 2015) and they've pretty much remained unchanged.

SECURE_HSTS_INCLUDE_SUBDOMAINS (False|True)
SECURE_HSTS_SECONDS (0|Integer)

Another setting was introduced in 1.11 not long after, and it authorizes the browser to preload the site in HTTPS.

SECURE_HSTS_PRELOAD (False|True)

You have to be careful with this one since it can really screw up your users if your site isn't ready to serve HTTPS for everyone. And, to properly enable preloading this way—hardcoded into a list in the browser—you have to submit your domain to the Chrome-managed list. That master list is used by all other major browsers as well.

Be warned! There are consequences to enabling this feature. If you have subdomains, clients, or partners who are relying on some HTTP-only webpages buried deep in the dusty recesses of a legacy app, they may get cut off from use for a long time. Hey, I've seen some strange things out there!

Can't Use a META in a Template

Many headers can be passed via meta http-equiv tags in a template. You can, for example, pass an expires header in a template to tell the browser to cache the content until some future date. But with the HSTS headers, the specification strictly forbids user agents to accept such implementations. Here are the exact words in IETF RFC 6797 on HSTS:

8.5.  HTTP-Equiv <Meta> Element Attribute
   UAs MUST NOT heed http-equiv="Strict-Transport-Security" attribute
   settings on <meta> elements [W3C.REC-html401-19991224] in received
   content.

That's it. No reason given, only that they must not honor the HSTS header if it's set this way.

Cautionary Tales and Recommendations

First of all, the whole point of HSTS is to get the browser to just load your site over HTTPS, right? To enable that, you have to make sure your certificates are valid. Invalid certs pop up all over the place, and with HSTS they won't fly! Your users will not be able to bypass the invalid certificates. Heck, why would you want them to anyway? It's not only insecure, but it's a bad user experience. Nowadays, making valid certificates is easy and often comes at no cost.

Second, take extra care if you are using includeSubdomains. Please make sure all subdomains have valid certificates or that they're included as subject alternate names (SANs) on your certificate. Alternately, you can use a wildcard certificate (*.yourdomain.com) to cover all subdomains if your security team is OK with that approach. This wildcard approach allows you to add subdomains without updating the certificate.

Third, the recommended approach for starting out with HSTS is to use a shorter max-age at first. Max-age directs the browser to cache the directive for that amount of time (in seconds) only. After max-age seconds, the browser may load over HTTP once again. At that time, it may receive and cache the directive once again unless you've removed or altered it by then. Therefore, starting with a short max-age can help avoid problems.

After checking with a short max-age, you can bump it up over time until you're certain there are no subdomains or clients that rely on the HTTP only endpoint for whatever reason. Chrome's HSTS preload site recommends starting with 5 minutes, then upping it to a week, 30 days, and finally 2 years. With such a long expiration, the browser would basically force-load the site every time over HTTPS for two years before it can try HTTP again. An undiscovered problem with such a long max-age can be as bad—or worse—than getting stuck with an unintended permanent redirect. Eek!

Add Automated Security Testing to Your Pipeline for Free

A Few Important Final Words

The whole point of HSTS is to get the browser to keep users safe from themselves. Let's say they're hanging out on public WiFi. And worse, they're connecting to your site using insecure, plain text HTTP because of a bookmark or an old link floating around somewhere. If they've been to your site over HTTPS recently, their browser will automatically go to HTTPS. Well, at least as long as it has an unexpired HSTS directive for your site in its cache.

If you have something that really needs to be secured, get on that preload list! That way, even if they haven't been before, they'll go to HTTPS anyway.

In general, you can implement HSTS headers on the web server or gateway if possible. Alternatively, add the settings values to your settings file. Do make sure your certificates are in order, and proceed with caution, especially if choosing to pursue the preload option.

This post was written by Phil Vuollet. Phil leads software engineers on the path to high levels of productivity. He writes about topics relevant to technology and business, occasionally gives talks on the same topics, and is a family man who enjoys playing soccer and board games with his children.


StackHawk  |  September 29, 2021