I’m excited for this post, because I get to introduce one of my best friends (and favorite coworkers) to the tutorialinux horde. I’ve been working with Christian in some form or another for several years now. We met while working at a startup in 2012, where he is the lead developer, and have worked on several projects since then. Although right now he gets paid mostly for programming work, he’s a longtime sysadmin and has been a huge influence on my growing taste for using FreeBSD systems in production.
You know those people who seem to have started in IT when they were still in diapers? That’s Christian. It’s my pleasure to welcome him as a contributor to tutorialinux. He’s got some fantastic stuff to share, and a huge amount of real-world experience to back up everything he teaches.
Lately, Christian and I have become a bit obsessed with encryption and HTTPS (going to far as to write a mini e-book about it, teaching people to set up TLS on their websites). Can you blame us? With the recent Internet security scares and the enormous push for TLS by organizations like Firefox, Tor, Google, Let’s Encrypt, and others, it’s definitely at the forefront of many system administrators’ and developers’ minds.
In these conversations about website security and HTTPS, you’ll often hear people talk about HTTP Strict Transport Security (HSTS for short). But what exactly is HTTP Strict Transport Security? How does it work? And how can you set it up in a few simple steps?
You’re about to find out.
Note: If you don’t care about any of the theory (bad sysadmin!) or are already familiar with it, simply scroll down to “Activating HSTS“.
Before we go into explaining what HTTP Strict Transport Security is about, let’s take a look at what happens when you visit a website that is HTTPS-only. It doesn’t matter which website it is — it could be Facebook, Google, your bank, or any other website that people typically visit using HTTPS.
For this example we are going to assume that you want to visit tutorialinux.com (it’s fairly certain that, very recently, you did). If you enter this into your browser’s URL bar, the browser will first try to connect to http://tutorialinux.com. It will then redirect you to https://tutorialinux.com/.
To see what exactly is going on here, we can use the ‘curl’ command line tool. Paste the following into your terminal:
curl -o /dev/null -I -v tutorialinux.com
The -I switch instructs curl to send HEAD requests (otherwise GET requests would be used), causing the server to only respond with HTTP headers.
The -v option stands for verbose, while -o /dev/null ensures that we don’t see the messages (and body, if there is one) outside of the verbose ones which are printed out to us on the shell.
Running this curl command will print a huge amount of information. Here are the interesting lines:
* Connected to tutorialinux.com (126.96.36.199) port 80 (#0) > HEAD / HTTP/1.1 > Host: tutorialinux.com > User-Agent: curl/7.47.1 > Accept: */* > < HTTP/1.1 301 Moved Permanently < Server: nginx/1.8.1 < Date: Sat, 20 Feb 2016 14:33:28 GMT < Content-Type: text/html < Content-Length: 184 < Connection: keep-alive < Location: https://tutorialinux.com/ <
The first character on each line shows what curl (your client, although this could also be your browser) is sending, and what the response of the web server you are talking to was. You can consider that first character to be an arrow that shows the direction, like “Client > Server” and “Client < Server”, the first meaning the client sends it and the second that the server sends it.
So what’s going on here? After connecting to tutorialinux.com the client sends a HEAD request to the / path. This is because the actual URL is http://tutorialinux.com/. HTTP clients will add this trailing slashes automatically, as it makes no sense to not request any page at all.
HTTP/1.1 is just an indication of the supported HTTP protocol. This isn’t of interest for us here. The next line, Host: tutorialinux.com, just specifies that we want to contact tutorialinux.com, not any other website that might be running under the same IP (that is, on the same web server).
In the server’s response we see that the status code 301 Moved Permanently is returned by the server for our client’s request. This means “we hit a redirect.” Redirects, both permanent and temporary, require a Location header to be set. This specifies where the redirect actually goes. In our case, the redirect sends us to https://tutorialinux.com/, which causes us to end up on the HTTPS version of the tutorialinux website.
Now let’s take a quick look at this from the perspective of an attacker. Imagine that this little client-server connection wasn’t for tutorialinux, but your bank account. First, connecting to an insecure website and only using TLS after the first request is a problem — it leaves room for hijacking your connection before you’ve set up an HTTPS connection.
Since normal HTTP connections are neither encrypted nor authenticated (meaning data can be modified without anyone noticing) an attacker could either prevent the use of HTTPS completely or redirect to their own HTTPS-enabled server which happens to serve a website that looks exactly like your bank.
This would effectively allow an attacker to launch a Man in the Middle (MitM) attack by talking to your browser on the one end (using HTTPS), while also forwarding your data to the bank’s website (also over HTTPS) thereby fooling you into thinking that you’re securely connected.
While the scenario of “proxying” the connection is more difficult to work around, it can be detected. Many banks provide Extended Validation (EV) certificates for exactly this reason. These certificates are typically more expensive, but show the company’s name in the URL bar (Paypal is a good example of this). Given that the Certificate Authority (CA) is trustworthy, making sure that this name is always displayed is a good way to detect such attacks. Other work-arounds include
- Checking the certificate fingerprints.
- Various web-of-trust-style protocols.
- A system called key pinning.
Perhaps we’ll write about these more in another article; going into each one in detail in this post is a bit too much information for right now.
HTTP Strict Transport Security enforces the use of HTTPS, but doesn’t make any future guarantees.
How does it work?
HSTS is just a single HTTP header, sent by the web server, which tells a browser or other HTTP client to always visit the given website with HTTPS (and never using plain, non-encrypted HTTP).
In its simplest form, it looks like this:
What this line states is that this domain should always be visited using HTTPS for the next 10,886,400 seconds (that’s about four months).
There are also some optional parameters which can be specified like this:
Strict-Transport-Security: max-age=10886400; includeSubDomains; preload
includeSubDomains means that when example.org gets visited and has this header, all subdomains (eg. www.example.org, blog.example.org) should also have HTTPS enforced. The preload flag is not part of the official standard, but you’ll learn more about it before this article is finished.
Enabling HSTS in nginx
Since all that’s required for HSTS to work is the header (mentioned above) has to be set, there is no logic involved on the server side. This makes activating the feature trivial on a web server. In the nginx web server, all that’s required is adding a single line to a server section, like this:
add_header Strict-Transport-Security "max-age=10886400; includeSubDomains; preload" always;
This both activates HSTS, and activates it for all subdomains. However, to make sure it is always being used you should still add this header to each subdomain’s server block as well, so HSTS will be activated as soon as any of them is visited.
The nginx always flag ensures that the header will be set, even if the status code which is returned to the client indicates an error. More on this can be found in the nginx documentation.
So what’s this mysterious preload flag all about? Not being part of the official standard, the preload flag is an invention by Google. It indicates that it should become part of the HSTS preload service, which is a list of domains that browsers should always visit using HTTPS, even when they weren’t visited before and therefore have had no chance to show the header to the browser.
This is to prevent attackers from stripping this header if they happen to be performing a Man-in-the-Middle attack the very first time that some unfortunate client visits the website.
So how does one get onto this list? First of all, both the includeSubDomains and the preload flags must already be activated. Other than that, the max-age parameter must be set to 10886400 or higher. This is a requirement by the service. After you’ve met those requirements, you can enter your domain on the HSTS Preload Submission website.
Once you’re verified, future browser versions will include your domain in this list.
Please consider that if you enable HSTS you should be sure that HTTPS will remain enabled. If you simply disable HTTPS again then people who accessed your website when the header was set won’t be able to connect to your website at at all.
HSTS is part of retrofitting our insecure Web with clever technology to make it better for everyone (except for the bad guys). If you own a website and have set it up to serve HTTPS already, enabling HTTP Strict Transport Security is another great step you can take to protect your visitors.
Thanks for helping to make the Web a better place!