Ensuring SSL via HSTS
My wife and I use a certain vacation website to take epic vacations where we pay mostly for the hotel and get event tickets (e.g. Disney World tickets) free. I love the site-- but sometimes I'm paranoid using it. Why? Here's why:
No pretty lock.
You say: but were you on the checkout screen or account areas?
No.
You say: Then who cares? You're not doing anything private.
Imagine this: You're on epic-travel-site.com and you're ready to create an account. So, you click on create account. You now see the pretty SSL lock. You also see where you to in your personal information and password. You put in the info and hit submit. All is well.
Well, not so much: what you didn't notice was that page with the "Create Account" button was intercepted and modified so the "Create Account" link actually took you to epic-travel-site.loginmanagementservices.com instead of account.epic-travel-site.com.
Even then, you'll never know if that's wrong: perhaps they use loginmanagementservices.com for account management. Many sites I go to use Microsoft, Google, or Facebook for account management. It could be legit or you could have just sent your password to was owned by an information terrorist punk with a closet full of Guy Fawkes masks.
301
SSL isn't just about end-to-end encryption, it's also about end-to-end protection. You need SSL at the start.
This is easy to enforce on every webserver. On IIS it's a simple rewrite (chill, getting to Linux in a bit):
<system.webServer> <rewrite> <rules> <rule name="Redirect to https"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="Off" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" /> </rule> </rules> </rewrite> </system.webServer>
Note: rewriting and routing are not the same. Rewriting is a very low-level procedure. Routing is application-level.
Using this, when you access HTTP, you'll get sent over to HTTPS. This is a 301 redirect.
That's great, but that still gives the Guy Fawkes-fanboys an opportunity to give you a horrible redirect; it's pretty easy to swap out.
307
So, let's upgrade our security: HTTP Strict Transport Security ("HSTS").
This entire thing is mostly TL;DR. For more detailed information about this and other more hardcore security techniques, watch Troy Hunt's Introduction to Browser Security Headers course at Pluralsight.
The essence of this is simple: the browser will send a Strict-Transport-Security header with a max-age in seconds. This will tell your browser to ignore your requests for HTTP and get HTTPS instead.
What browsers can use HSTS today? Go look:
See also: http://caniuse.com/#search=hsts
If your browser doesn't support HSTS, you need to keep up with updates. Get with it. But, whatever: if you're ancient browser doesn't support HSTS, nothing will break; you just won't receive any benefit.
What's this Strict-Transport-Security header do? Try to access the HTTP version and see:
It's not going to bother with the 301 roundtrip; instead, the browser will just do an "Internal Redirect". 307 means "Internal Redirect".
To put it simply: a 301 is HTTP then HTTPS and a 307 is only ever HTTPS. The existence of HTTP is not even acknowledged.
For clients, this is a major security boost. For servers, this is both a security and performance boost: you are no longer handling all those HTTP redirects! Some politician/salesman would say: "It's like getting free bandwidth!"
Testing HSTS (Chrome)
Lesson: for each web site, start with a 2 day max age and slowly grow.
While removing HSTS for others can be a hassle, removing HSTS during testing and development for yourself is simple:
In Chrome, you go to chrome://net-internals/#hsts.
You'll see delete and query. Throw your domain into query to see it. Throw it into delete to delete it.
Testing HSTS (Firefox)
For the 2-3 people left who think Firefox stills matters,
First, know that you aren't going to see a 307. It's an internal redirect anyway. It's hardly a real header. In Firefox, you see,
Second, to test this stuff, you'll need Firebug and to make sure that persist is enabled (with this you can see directs, not just data for currently loaded page; use clear to clear console):
Third, while Fiddler's disable-cache is great for most days, Firefox throws a bad fix for invalid certs. So, disable cache in Firebug:
Now you'll be able to test HSTS in Firefox. Once you can verify that you can see the header and the redirect, you can have certainty of it's removal.
To remove HSTS locally, look for the SiteSecurityServiceState.txt file in your Firefox profile. Where's THAT? I'm not about to remember where it is. I'm saving that brain space for important thing. On Windows, I just run a quick Agent Ransack search (a tool which should be part of your standard install!). You could quickly find it with PowerShell as well:
ls $env:appdata\Mozilla -r site*
On Linux, it's a freebie:
find / -name SiteSecurityServiceState.txt
Once found, you apparently have to exit Firefox to get it to flush to the file. Then, you can edit your domain out of the file.
TOFU
Now to upgrade the security again...
That great, but you still have to trust the website on first access (often called the TOFU problem: trust on first use; remember: no sane person likes tofu). You accessed HTTP first. That may have been hacked. The well may have been poisoned. It's the same hack as in the account management example; it's just moved a little earlier.
The solution actually quite simple: have the fact that you want HTTPS-only baked into the web browsers. Yeah, it's possible.
You do this by upgrading your security again, then submitting your site to https://hstspreload.appspot.com/.
The upgrade requirements are as follows:
- Have an SSL cert (duh!)
- Use the SSL cert for the domain and all subdomains (e.g. account., www., etc...)
- Upgrade the HSTS header to have a max-age of 126 days
- Upgrade the HSTS header to enforce HSTS for all subdomains
- Upgrade the HSTS header to enable preloading
The ultimately boils down to this:
That's it.
It says 126 days, but https://www.ssllabs.com/ssltest/ gives you a warning for anything under 180. Just do 180.
Just submit your site to https://hstspreload.appspot.com/ and you'll be baked into the browser (well, browsers depending on how much it's shared). It will tell you about any potential errors.
Nobody will see this until updates come through. This is one reason updates and important.
How can you prevent just anyone from submitting you? You can't. By adding preload
, you stated that you wanted this. The hstspreload website will make sure you want this before bothers doing anything with it.
ASP.NET WebApi / ASP.NET MVC
How do you add this to my website? You add it the same way you add any header. If you are using ASP.NET MVC or ASP.NET WebApi, you just create a filter.
ASP.NET WebApi:
public class HstsActionFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute { public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext) { actionExecutedContext.Response.Headers.Add("Strict-Transport-Security", "max-age=10886400"); } }
ASP.MET MVC:
public class HstsActionFilterAttribute : System.Web.Mvc.ActionFilterAttribute { public override void OnResultExecuted(System.Web.Mvc.ResultExecutedContext filterContext) { filterContext.HttpContext.Response.AppendHeader("Strict-Transport-Security", "max-age=10886400"); base.OnResultExecuted(filterContext); } }
What do you do if you enforce HTTPS then find out later that the client decided that being hacked is cool and wants to remove HSTS and SSL?
Well, first you get them to sign a releasing stating that you warned them about security and they threw it back in your face.
After that, set max-age to 0 and hope every single person who ever accessed yourself comes back to get the new header. After that, removing the header and SSL. In reality: that's not going to happen. The people who didn't get the max-age=0 header will be locked out until the max-age expires.
NWebSec
Filters for ASP.NET MVC and ASP.NET WebApi are great, but the best type of coding is coding-by-subtraction. Support less. Relax more. To this end, you'll want to whip out the NWebSec
nuget package.
Once you add this package, your web.config file will be modified for the initial setup. You just need to add your own configuration.
Here's a typical config I use for my ASP.NET WebAPI servers:
<nwebsec> <httpHeaderSecurityModule xmlns="http://nwebsec.com/HttpHeaderSecurityModuleConfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="NWebsecConfig/HttpHeaderSecurityModuleConfig.xsd"> <redirectValidation enabled="false"> <add allowedDestination="https://mysamplewebsite.azurewebsites.net/" /> </redirectValidation> <securityHttpHeaders> <x-Frame-Options policy="Deny" /> <strict-Transport-Security max-age="126" httpsOnly="true" includeSubdomains="true" preload="true" /> <x-Content-Type-Options enabled="true" /> <content-Security-Policy enabled="true"> <default-src self="true" /> <script-src self="true" /> <style-src none="true" /> <img-src none="true" /> <object-src none="true" /> <media-src none="true" /> <frame-src none="true" /> <font-src none="true" /> <connect-src none="true" /> <frame-ancestors none="true" /> <report-uri> <add report-uri="https://report-uri.io/report/6f42f369dd72ec153d55b775ad48aad7/reportOnly" /> </report-uri> </content-Security-Policy> </securityHttpHeaders> </httpHeaderSecurityModule> </nwebsec>
The important line for this discussion is the strict-Transport-Security
element.
NWebSec works by days, not seconds. Just keep that in mind.
The content-Security-Policy
is also critical for solid security, but that's a discussion for a different day. Just keep in mind that the previous example was for an API server. ASP.NET MVC would require you to open access to images, scripts, and other browser-related entities.
Nginx
Lets say you don't want to add this to your website. Wait, why not? Here's one reason: you don't have access to SSL on the box, but you do somewhere else. Azure comes to mind: you have a free-tier website, but want SSL with a custom domain. You'll have to use nginx for SSL termination with Varnish (caching server) to offload a lot of traffic from Azure.
Think it through: varnish is in front of your website providing caching. What about putting something in front of it to provide SSL? Easy. That's called SSL termination. Before I got completly sick of ASP.NET and rewrote everything in Python/Django, I did this for most of my ASP.NET web sites.
In your Nginx config, you can do something like this:
As usual, Linux is more elegant than .NET/Windows. Just a fact of life...
Production
When you implement SSL, don't celebrate and advertise the greatness of your security milestone. You only made it to the 1990s. That's nothing to brag about. You need to hit HSTS before you hit something anything close to modern security. If you are doing any law-enforcement or finance work, you need to implement HPKP before you hit the minimum required security to earn your responsible-adult badge.
Even then, if you ever want to ruin your life, use the standard development SDLC for these features: do it in development, give it to QA, throw it on a staging environment, then push to production. You will ruin your entire company. Why? Because you have to slowly implement these features in production.
Here's one route to take: run HSTS through your SDLC (starting with staging) with a 2 second max-age. Yes, it's pointless, but it will tell you if it's working at all. Give it a week. Jump to 5 seconds. Give it two weeks. Jump to 30 seconds. At this point, possibly give it a few months. Slowly increase, slowly add browser-upgrade-recommendations to your web site, and slowly give clients warnings (HSTS is passive and won't ever kick anyone out; but you want them to be secure, so inform them!)
Eventually, you'll get to a 128 model where you can add the preload option and request that your site be baked into the browsers themselves.