Published on

HSTS (HTTP Strict Transport Security)

Authors
null

Learn why HTTPS is not enough to protect your website from network attacks and how the HSTS header comes in to solve the problem. Let's begin!

What is HSTS?

HTTP Strict Transport Security is an opt-in browser security feature that prevents browsers from making any unencrypted connections to a domain.

By unencrypted connections I mean using http instead of https (or ws instead of wss for WebSockets).

You can enable the protection for your website with the Strict-Transport-Security header like so:

Strict-Transport-Security: <options>

There are 3 options, max-age, includeSubdomains and preload. We'll get to those in a minute, but first, let me explain why it is so important that you implement HSTS in your web application.

Why is HSTS important?

The HSTS header prevents network attacks against your web application. If you are not using it, here is how your application might work:

Scenario 1: No HSTS, No Attacker

  1. The user types in www.example.com
entering www.example.com in the URL bar
  1. The user's browser sends an unencrypted HTTP request to http://www.example.com/
unencrypted http request
  1. The webserver returns a redirect to https://www.example.com
redirect to https
  1. The user's browser sends an encrypted HTTPS request to https://www.example.com/.
encrypted http request
  1. The webserver returns with the login page.
encrypted login page
  1. The user enters their username and password, which the browser safely sends to the webserver across a secure TLS connection.
encrypted login request

But what if there is an attacker on the network? HTTPS doesn't help. Let me show you what I mean.

Scenario 2: No HSTS, Attacker on the network

  1. The user types in www.example.com
entering www.example.com in the URL bar
  1. The user's browser sends an unencrypted HTTP request to http://www.example.com/
unencrypted http request
  1. The attacker intercepts this unencrypted request and returns the login page from the real server. Crucially, the connection is still unencrypted.
unencrypted http request
  1. The user gives their username and password straight to the attacker over the unencrypted connection.
unencrypted http request

Oops. That's not good.

Now let's see what happens when the HSTS header protects the web application.

Scenario 3: HSTS

  1. The user types in www.example.com
entering www.example.com in the URL bar
  1. The website uses HSTS, so the user's browser right away sends an encrypted HTTPS request to https://www.example.com/. The attacker doesn't get a chance.
encrypted http request
  1. The webserver returns with the login page.
encrypted login page
  1. The user enters their username and password, which the browser safely sends across a secure TLS connection.
encrypted login request

See how HSTS prevented the attack? But now you may be asking...

What if the user is visiting the website for the first time?

An excellent question! If the user is visiting the website for the first time, the user's browser hasn't had the chance to see the HSTS header yet.

In this case, the attack is still possible because the attacker takes over the connection before the actual web server gets a chance to tell the user's browser to use HSTS.

Luckily, there is a solution, and I promise you'll know everything about it by the end of this article. It has to do with the third option, preload, but let's look at the other two options first!

The max-age parameter

The first parameter in the HSTS header is max-age. It is the amount in seconds for how long you want browsers to remember the header once they see it.

For example, the following header would enable HSTS for one minute for the domain that sends it. The browser would then, for 60 seconds, refuse to make any unencrypted connections to the domain.

Strict-Transport-Security: max-age=60

Such a short time is generally not very useful. It's more common to see values for a year or two years like so:

# 1 year
Strict-Transport-Security: max-age=31536000
# 2 years
Strict-Transport-Security: max-age=63072000

Browsers will refresh the time every time they see the header.

☝️ Note

When implementing HSTS in a production environment, it's good to start with a small max-age and then slowly ramp it up to a year or two years. This way, you can quickly recover if something breaks.

Canceling HSTS

The second use-case for the max-age parameter is to cancel HSTS if you want to get rid of it. By returning max-age of zero, browsers will remove the protection once they see the header.

# Cancel HSTS
Strict-Transport-Security: max-age=0

The includeSubdomains parameter

The second parameter is includeSubdomains. By default, the HSTS header will only affect the domain that serves it. That is, if https://www.example.com sends it, then only www.example.com will be protected, foo.www.example.com will not.

To include subdomains, add includeSubdomains like so:

# Store for 2 years, apply also to subdomains
Strict-Transport-Security: max-age=6307200; includeSubdomains

☝️ Note

The includeSubdomains directive is a requirement for preloading, which we'll look at next.

HSTS preloading

The third parameter, preload, facilitates HSTS preloading.

As I mentioned earlier, preloading is a mechanism that you can use to protect your website with the HSTS header even when the user is visiting the website for the first time.

It works so that you tell Google to protect your website, and they will see that all of the major browsers will change their source code to preload the HSTS header for your domain.

No joke! You can see the JSON file right here. If you look carefully, you will find appsecmonkey there!

{ "name": "appsecmonkey.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },

There are a couple of prerequisites for enabling preloading for your domain, though.

First and foremost, you have to serve an HSTS header with the preload directive. Additionally, the max-age has to be >= a year, and you have to add includeSubdomains.

# Store for two years, also apply to subdomains, enable preloading
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Also, your domain must return a valid TLS certificate on the HTTPS port (443) and redirect to HTTPS on port 80 (if port 80 is enabled).

When you meet these requirements, go to https://hstspreload.org/ and submit your domain like so:

hsts preload submission

Conclusion

HTTPS is not enough to protect the users of your web application from network attacks. And as long as there are unencrypted websites on the Internet, browser vendors cannot merely disable unencrypted connections altogether.

Luckily there is an opt-in mechanism for websites that don't want any unencrypted connections. And that is the HSTS header.

When implementing HSTS in production, it's best to start with a slow max-age and slowly ramp it up.

Finally, it's possible (and highly recommended) to preload your HSTS header into the major web browser's source code by submitting your domain to hstspreload.org. But think it through before you do so; canceling is a slow and painful process.