Understanding HSTS

Before enabling HSTS it's really important you understand how it works and how it might affect your websites.

Photo of Greg Harvey
Wed, 2016-09-14 09:24By greg

So I made a slightly sarcastic tweet the other day which deserves expanding upon.

Every pentest report we've seen this year says something about "enabling HSTS on the web server", like it's some stock thing pentesters are adding these days. Which is fine in and of itself, but the problem is in the reports I've seen they don't always explain what HSTS is and why (or indeed, why you shouldn't) enable it. For example, here's a snip from one such report:

There is no HSTS (HTTP Strict Transport Security) policy. An NGINX header for “Strict-Transport-Security” should be added instructing browsers to access the site only over HTTPS.

That's it. No further expansion.

So what does HTTP Strict Transport Security even mean? It's basically a means for your website to tell HSTS compatible Internet browsers (and other applications that make HTTP requests) that they must use HTTPS (a secure encrypted connection over SSL) to talk to this website. So for example, if I enable HSTS on the web server serving www.codeenigma.com, http://www.codeenigma.com will no longer be permitted, only https://www.codeenigma.com - note the 's'. Sounds sensible, right? Enforce secure connections on the client side. Neat.

(Here's the full formal specification, for anyone who's interested.)

The way it works is when a web browser hits http://www.codeenigma.com, the server sends a "header" - a piece of information for the browser to read - that tells it the website at www.codeenigma.com should only be served over HTTPS. If the browser is HSTS compatible (most are nowadays) then it remembers this setting for the future and it will always request a secure connection to www.codeenigma.com (unless you switch HSTS off on the web server and delve into the innards of the browser and clear the HSTS setting). It also pushes you automatically on to https://www.codeenigma.com, even though you didn't request the 's' initially.

Now, if you have some limited information from a pentester recommending you enable HSTS, and you decide to go ahead and use the default recommended settings, then you will enable something called 'includeSubdomains' and you will set the 'max-age' - the effective time to live (TTL) of the header - to 6 months. You will do this, because that's what everyone says to do. I've seen pentesters include headers that do this in reports, as copy and paste config to use. The Qualsys SSL scanner complains if 'max-age' is too low, which is sensible - HSTS is less effective if the header expires too fast, it gives would-be attackers a potential window of opportunity to carry out a MITM attack.

But this changes things. Suddenly it's not just www.codeenigma.com that gets affected. The entire codeenigma.com domain has HSTS enabled for it, regardless of how many sites there are and where they are hosted. Now your HSTS compatible browser, once it's received the header from www.codeenigma.com, will always request a secure connection to anything.codeenigma.com, which is a whole other proposition.

Can you see the potential problem yet? It's a big one! It focuses around the 'anything.codeenigma.com' bit and it goes like this:

HSTS tells the browser that all websites on this domain must be served over HTTPS from this point forward. This is fine if you know all your web-based applications absolutely, definitely allow HTTPS connections - in that case, by all means enable HSTS. In fact, it's a good idea. However, if you're a large organisation with literally hundreds, if not thousands of applications and assets served over HTTP, you cannot know if they are all accessible over HTTPS. And if you enable HSTS for some sites, you will potentially break the availability of any assets that are not available over HTTPS to anyone with a HSTS enabled web browser. Because once that HSTS setting is in place in user browsers they will always demand a HTTPS connection, whether it is available or not.

So imagine a scenario briefly. I have these web applications (I'm making this up, of course):

https://www.codeenigma.com
https://foo.codeenigma.com
http://api.codeenigma.com

'www' is my main website, 'foo' is some marketing site or a dashboard or something and 'api' is some documentation for my continuous integration scripts. 'www' and 'foo' both use HTTPS, but for whatever legacy reason I never set up HTTPS for 'api', it's public information, I don't really care...

I ask a pentester to test www.codeenigma.com, she does and she sends me a report - part of which says "Enable HSTS headers in your web server" and provides me with a copy and paste config which sets 'max-age' to 6 months and the 'includeSubdomains' option. I quickly read what HSTS is, it sounds sane, I copy and paste the config, thus enabling it. Later that day people start phoning because they can't access api.codeenigma.com any more, because:

  1. they visited https://www.codeenigma.com;
  2. they received the HSTS header and their browser saved the information that it must always demand a HTTPS connection from anything codeenigma.com;
  3. they then visited http://api.codeenigma.com (note, no 's') and their browser tried to take them to https://api.codeenigma.com.

Oops! https://api.codeenigma.com doesn't exist!

Now in that scenario it's easily fixable, enable HTTPS for the API docs site. But imagine that x100 at a large government body or a global multinational company? And worse, you can't pull this back - remember, you followed the defaults, so even if you disable HSTS again immediately on the web server, that setting will persist for 6 months in the browser of anyone who hit the HSTS enabled website. Pandora's box is open, the stable door is flapping in the breeze, because it's a client browser thing. The only way to "fix" it is to either go around all the affected applications and enable HTTPS (hopefully you even can!) or tell all affected users they need to figure out how to clear their local HSTS settings because you screwed up.

Someone's gonna get fired for that!

So yes, HSTS is a good idea in principle. But one does not simply enable HSTS.

 

Quick postscript here - during my research for this blog, I fell upon a question. What happens if I enable HSTS on the web server for 'foo.codeenigma.com' with the 'includeSubdomains' option passed in the header? Will I still affect 'api.codeenigma.com'? The answer seems to be nobody really knows! Yay!

I found someone on StackExchange quoting the official specification and claiming it should be fine, because in this context it will only affect 'anything.foo.codeenigma.com', but then the very next poster says "woah, not necessarily!" ... and the Nginx implementation guide (which is very good, by the way) implies 'includeSubdomainsanywhere will affect everything.

Logically the latter makes some sense, because HSTS is more likely to be applied to 'www.example.com' and if 'includeSubdomains then only applies to 'anything.www.example.com' then it's a fairly useless option! But then maybe the implementers assume web server owners know what they're doing and will have apex domain handling sorted out?

And then there's the question of who the implementers are... hands up those who've seen "standards" implemented differently in different browsers before? Oh, everybody! So just because the IETF intended something to be done a certain way, that doesn't mean Microsoft, Google, Apple, and so on have the same view. Their interpretations may vary.

In short, I think the only safe position is never use 'includeSubdomains in your HSTS header unless you know all the applications hosted anywhere in that domain support HTTPS. I don't believe there's any other safe approach.