HSTS Header (Strict Transport Security) Explained
Tags: webdev, security, programming, cybersecurity
Published: 2021-03-06 11:34:27

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!

The original article can be found here: [HSTS on AppSec Monkey](https://www.appsecmonkey.com/blog/hsts/).

## 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: 

```yaml
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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrubnh7yfsi966hv61u2.png)

2. The user's browser sends an unencrypted HTTP request to http://www.example.com/

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nd2j6dycyrqzb2psdzj9.png)

3. The webserver returns a redirect to https://www.example.com

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4ezl6iv3ib7uq4mka97.png)

4. The user's browser sends an encrypted HTTPS request to https://www.example.com/.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mwnxfonxaa47lkici1o.png)

5. The webserver returns with the login page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3t6t22wg2k031bsv9dz.png)

6. The user enters their username and password, which the browser safely sends to the webserver across a secure TLS connection.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jo5g9xsmfsm74p2cbw4y.png)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbu0hvzzqz13lvc8dclg.png)

2. The user's browser sends an unencrypted HTTP request to http://www.example.com/

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apxpysxfp5jkao6strgv.png)

3. The attacker intercepts this unencrypted request and returns the login page from the real server. Crucially, the connection is still unencrypted.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54at2jq43yizp15wt6bn.png)

4. The user gives their username and password straight to the attacker over the unencrypted connection.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tk273i2j72lf4a9hvg6k.png)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbu0hvzzqz13lvc8dclg.png)

2. 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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31qrb8mueayx6qqxy5mw.png)

3. The webserver returns with the login page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uas4od3hpghf0k24sv14.png)

4. The user enters their username and password, which the browser safely sends across a secure TLS connection.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d7b3pmye52a8tl31n40j.png)

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.

```yaml
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: 

```yaml
# 1 year
Strict-Transport-Security: max-age=31536000
```

```yaml
# 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.

```yaml
# 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: 

```yaml
# 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](https://raw.githubusercontent.com/chromium/chromium/master/net/http/transport_security_state_static.json). If you look carefully, you will find appsecmonkey there!

```json
{ "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`.

```yaml
# 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/](https://hstspreload.org/) and submit your domain like so:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yre001ol2sivsdzmijfb.png)

## 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.

## Get the web security checklist spreadsheet!
[![Subscribe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iqd1ef4a5a0icyvvk65.jpg)](https://eepurl.com/hqAGt5)
 ☝️ [Subscribe](https://eepurl.com/hqAGt5) to AppSec Monkey's email list, get our best content delivered straight to your inbox, and <b>get our 2021 Web Application Security Checklist Spreadsheet for FREE</b> as a welcome gift!

## Don't stop here
If you like this article, check out the other application security guides we have on [AppSec Monkey](https://www.appsecmonkey.com/) as well.

Thanks for reading.