During my travels over recent weeks I’ve been doing a quick demo that works like this:
First, I open up the dev tools in Chrome and select the network tab.
Second, I load up americanexpress.com and show the network requests:
I point out how the first one goes out over HTTP because this is what browsers do when you don’t explicitly enter a scheme such as “https://”;. The server responds to this request with an HTTP 301 “Moved Permanently” and a “location” header which tells the browser to go back and request the resource securely:
If I’m feeling adventurous, I’ll show this pattern whilst connected to the Wifi Pineapple and allow it to serve up a page like this instead:
What you’re seeing here is a Marty from Madagascar wearing a polka dot afro and dancing around to a very loud rendition from this clip. A more nefarious “man in the middle” would instead substitute a phishing page asking for your AMEX credentials which many people would happily provide because, well, it’s the correct URL in the address bar!
Now let’s try this again with Have I been pwned? (HIBP), my site for monitoring accounts impacted in data breaches:
See that? The first request is the same pattern as earlier (no scheme so defaults to HTTP), but the response status is 307 “Internal Redirect”. This is Chrome saying “I’m not even going to issue that request, instead I’m going to change it to HTTPS then try again” which is what gives us the second request. This is key: Chrome has refused to issue the first request over the insecure HTTP protocol. How is this so? The response header returned by the second request explains why:
This is the Strict-Transport-Security response header or as we otherwise know it, HSTS (HTTP Strict Transport Security). Once this header is returned by the site, the browser will not make an HTTP request to the site no matter how hard you try and instead it’ll do that 307 from the earlier screen grab. It’ll do that for the number of seconds described in the “max-age” attribute (that 315… value is one year in seconds) after which the browser could make an insecure request… until the response header is set again the next time the above process is repeated. I’ve added the “includeSubdomains” attribute too so it applies to [anything].haveibeenpwned.com.
But there’s a problem – what if you’ve never seen the HSTS response header? That very first request is insecure and you’ve got the earlier AMEX problem all over again. What’s more, the HSTS response header has to be sent over HTTPS so the initial insecure request can’t return it and even if it did, when the concern is a man in the middle then they could simply strip it out from the response header anyway.
This is why I also have the “preload” attribute in the header above and it means I can then go over and submit the URL to the HSTS preload site:
Now it gets a bit interesting. The site above is run by Chromium and by submitting a domain here you’re asking for it to be preloaded into the browser itself. In other words, when the browser ships then your site will already be specified as only being accessible over HTTPS even if you’ve never visited it before. This means that the risk described above where the first request is insecure and HSTS is dependent on a secure response is gone – the browser will internally redirect to the secure scheme before anything hits the wire.
In order to preload HSTS into the browser though, there are a few criteria that need to be met:
- Have a valid certificate.
- Redirect all HTTP traffic to HTTPS—i.e. be HTTPS only.
- Serve all subdomains over HTTPS, specifically including the www subdomain if a DNS record for that subdomain exists.
- Serve an HSTS header on the base domain:
- Expiry must be at least eighteen weeks (10886400 seconds).
- The includeSubdomains token must be specified.
- The preload token must be specified.
- If you are serving a redirect, that redirect must have the HSTS header, not the page it redirects to.
All this is just fine but there’s a “here be dragons bit”: Only do this if forever and a day you are happy to serve your site over HTTPS and will never again need the insecure scheme. You can see the problem – if the browser simply cannot make an insecure request and for whatever reason your site no longer supports HTTPS in the future, you’re screwed. It’s the same even if you’re using HSTS and don’t preload (albeit slightly less severe) because anyone who has previously been to your site within the max-age period now can’t make requests. Be really sure that plain old HTTP with no SSL is dead before enabling this.
Moving on, once a site is submitted there’s a review process after which it eventually appears in Chromium’s HSTS preload list:
You’ll also see it filter through to Mozilla’s:
Now there are a couple of noteworthy things here. Firstly, both lists contain different sites – it’s not entire parity. When you check out the Chromium one you’ll see a huge array of Google domains that aren’t all present in Mozilla’s list, for example. They should both source submissions from the HSTS preload site above, but they can always add their own custom list as well.
Secondly, this is kinda depressing! Why? Because if we take Chromium’s list as it stands today, there are less than three thousand sites worldwide demanding a secure connection and a significant portion of those are Google’s. None of our “Big 4” Aussie banks are on there, for example. You can find yaporn.tv on there (I won’t link to it, you can figure out the purpose by name…) but we can’t get a bunch of major banks on-board! It makes me think back to my recent bank grade security post; a Swiss porn site is doing a better job of enforcing a secure connection than a bunch of multi billion dollar financial institutions!
Moving on, it’s great seeing HIBP slated for preloading, but of course the list above still needs to make its way into builds of Chrome and Firefox. At the time of writing, it’s not in the current Chrome release which is version 43. You can query a domain by going to chrome://net-internals/#hsts then entering it towards the bottom of the screen:
Note that if you’ve visited the site before you should see it appear in the query due to the earlier mentioned response header. But you can delete these hence the “Not found” result above (I assure you, I have been to my site before)! However as you can see in the “Delete domain” section:
You cannot delete preloaded entries
So hurry up and wait is the answer. Whilst waiting, at least the site is protected after that initial request which is the right thing (and indeed the necessary thing to get preloaded) in the immediate term.
And what about good old Internet Explorer? As much as people love to hate it, as of June 9 IE11 supports it (note also Microsoft’s mention re sourcing from Chromium’s HSTS preload list show earlier). You’ll also find “Can I use” reflecting this support already:
Of course it will also be supported in Microsoft’s next gen “Edge” browser in Windows 10 too so as of now we can confidently say that all major browsers support HSTS. If someone with an old one hits your site then it simply ignores the header.
Speaking of Microsoft, if you’re living in an ASP.NET world then definitely check out NWebsec on NuGet by André Klingsheim. This makes it dead easy to add the HSTS header as well as a bunch of other really neat security headers. Regardless of your language of choice though, it’s just a response header so you can return it any old way you like.
And before someone leaves a comment to this effect, yes, I’ll add an HPKP (HTTP Public Key Pinning) header to HIBP in the future. It requires a little more planning though and it mitigates a very different risk so I’ll save that for another blog post.
So that’s HSTS and how to preload it. As HTTPS becomes more ubiquitous across the web, this feature should really start to gain traction and hopefully initiatives like Let’s Encrypt will help expedite that (note also that this is now being reported as “Arriving September 2015”). It’s good times for those wanting to further protect their web assets and not so good for those wanting to intercept other people’s traffic (hi David Cameron!)