As an experiment, please take a moment to type your bank’s URL into a browser.
If you are anything like the vast majority of users, you wrote the URL without specifying the protocol scheme, that is you typed mybank.com rather than https://mybank.com. Your browser, without seeing the protocol scheme, sent your request over HTTP. Upon receiving that request, your bank’s web server, knowing that it is insecure to communicate in plain text over an untrusted network, responded with a redirect telling the browser to resend the request over HTTPS. Safe enough?
No, not at all. This switch over from HTTP to HTTPS, so common in practice, forces users to swim unprotected through the shark infested waters of the internet before reaching the safety of encryption. If an attacker takes over, the communications might continue over an insecure channel without you or your user ever realizing it. Traffic sent over HTTP is vulnerable to man-in-the-middle (MitM) attacks, putting your users’ credentials at stake. If this sounds far fetched, go watch Moxie Marlinspike explain sslstrip and return to this post feeling very, very afraid.
How can we break the dependence of HTTPS on HTTP? One way is to change the browser’s behavior so that it communicates securely from the very beginning. This is precisely the purpose of HTTP Strict Transport Security (HSTS). This HTTP header tells the browser that it should always access your site over HTTPS. Even if a user types http://yourdomain.com, the browser will internally change this over to https://yourdomain.com for all content in your domain.
Here’s an example of an HSTS header:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Note that max-age is a mandatory while includeSubDomains and preload are optional.
The max-age is a time to live in seconds that will be updated each time the browser receives a response with the header. Setting max-age to zero forces the browser to remove your site from its list of protected sites. The HSTS standard uses one year (approximately 31,536,000 seconds) as an example, which is a reasonable and widely used value.
Adding the includeSubDomains directive indicates that the browser should extend its protection to all subdomains of the current domain. While apparently straightforward, this setting requires some thought as there are a few gotchas.
Protecting subdomains could cause denial of access for any subdomains not supporting HTTPS or that depend upon resources not supporting HTTPS. So you’ll need to identify and test all subdomains before applying the policy.
Not protecting subdomains brings its own risks, most notably that of cookie hijacking. Depending on cookie settings, links from your protected domain to an unprotected subdomain could result in cookies being sent over HTTP, allowing an attacker to read the cookies. Given that cookies often contain session tokens, you’ve just enabled an attacker to bypass your login and access a user’s account.
Another risk of cookie hijacking occurs if an HSTS header protects a subdomain but not its parent. A trick to close this exposure is to include a reference to the root of your domain via an image tag (hidden via CSS) and including the HSTS header in the response, preferably with the includeSubDomains directive. But again, make sure to test all subdomains.
HSTS also protects users from themselves. Say a MitM attacker presents a fake certificate, one not signed by a trusted CA. Recognizing the certificate as invalid, the browser warns the user in big red letters and forces him to jump through a few hoops in order to proceed to the site. Despite the warning, the user thinks, hmm, that’s weird, but, hey, I’ll click through anyway because I’m short of time and want to get this done. Again, HSTS to the rescue. If the site is protected by HSTS, the browser will deny our reckless user any chance of clicking through.
We’re still left with a problem. What happens when somebody visits our site for the first time? Since the browser has yet to see our HSTS header, we’re back to starting the communications over HTTP. If an attacker intervenes at this moment, they’ve got us beat. So we need a way to tell the browser to access our site securely on the user’s first request.
HSTS preload has us covered here if we follow three steps. First, register your site with Google at https://hstspreload.appspot.com. This tells Google to hardcode HSTS protection for your site into the next update of Chrome. Internet Explorer and Firefox will follow suit by copying Chrome’s list. Second, add the preload directive to your HSTS token as in the example above. This signals your agreement to the HSTS preload registration. Third, assure that your site meets the requirements for HSTS preloading:
- Assure that your certificate is valid
- Direct all traffic to HTTPS
- Serve all subdomains over HTTPS
- Include the HSTS header on your base domain
- Set the max-age to at least eighteen weeks (10886400 seconds)
- Add the includeSubDomains directive to the header
Expect the mechanism for preloading to evolve. Listing all of the world’s domain names within each browser sounds less than feasible. It has only worked so far because not enough web developers have taken advantage of preloading.
While the header itself is straightforward, implementing HSTS does require going through a process to assure that your entire site works in HTTPS-only mode. Indeed, applying HSTS could be thought of as the final step in migrating away from mixed content, but it is key to assuring the effectiveness of HTTPS.
IETF Standard: https://tools.ietf.org/html/rfc6797
Browser Support: http://caniuse.com/#feat=stricttransportsecurity
Google Preload: https://hstspreload.appspot.com
Migrating from Mixed Content: https://https.cio.gov/mixed-content/