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.
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
- The user types in www.example.com
- The user's browser sends an unencrypted HTTP request to http://www.example.com/
- The webserver returns a redirect to https://www.example.com
- The user's browser sends an encrypted HTTPS request to https://www.example.com/.
- The webserver returns with the login page.
- The user enters their username and password, which the browser safely sends to the webserver across a secure TLS connection.
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
- The user types in www.example.com
- The user's browser sends an unencrypted HTTP request to http://www.example.com/
- The attacker intercepts this unencrypted request and returns the login page from the real server. Crucially, the connection is still unencrypted.
- The user gives their username and password straight to the attacker over the unencrypted connection.
Oops. That's not good.
Now let's see what happens when the HSTS header protects the web application.
Scenario 3: HSTS
- The user types in www.example.com
- 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.
- The webserver returns with the login page.
- The user enters their username and password, which the browser safely sends across a secure TLS connection.
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:
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 to AppSec Monkey's email list, get our best content delivered straight to your inbox, and get our 2021 Web Application Security Checklist Spreadsheet for FREE 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 as well.
Thanks for reading.
Top comments (5)
The first post I have seen on HSTS that I can safely say is fantastic, have a ❤ and a 🦄.
Most of them talk about the header and then forget the most important part, the preload bit.
Then if they do talk about preload they completely forget to tell people to ramp it up slowly as I have personally got this one wrong and it did take a while to fix (but mainly because I am rubbish at server administration 😋)!
Really valuable series man! Learned a ton! More in the pipeline?
Oh yeah (:
Cool! Are you on Twitter as well?
yeah I'm trying to be: twitter.com/TeoSelenius you would be my third follower!