Scott Hanselman

How to enable HTTP Strict Transport Security (HSTS) in IIS7+

June 06, 2015 Comment on this post [15] Posted in IIS
Sponsored By

I got a report of a strange redirect loop on a website I (inherited, but help) manage. The reports were only from Chrome and Firefox users and just started suddenly last week, but the code on this site hadn't changed in at least 3 years, maybe longer.

Chrome shows an error "this webpage has a redirect loop"

What's going on here? Well, it's a redirect loop, LOL. But what KIND of redirects?

We know about these redirects, right?

  • 302 - Object Moved - Look over here at THIS URL!
  • 301 - Moved Permanently - NEVER COME HERE AGAIN. Go over to THIS URL!

A redirect loop builds up in the Chrome Developer Tools

But there's another kind of redirect.

  • 307 - Internal Redirect or "Redirect with method" - Someone told me earlier to go over HERE so I'm going to go there without talking to the server. Imma redirect myself and keeping using the same VERB. That means you can redirect a POST without the extra insecure back and forth.

A 307 Internal Redirect

Note the reason for the 307! HSTS. What's that?

HSTS: Strict Transport Security

HSTS is a way to keep you from inadvertently switching AWAY from SSL once you've visited a site via HTTPS. For example, you'd hate to go to your bank via HTTPS, confirm that you're secure and go about your business only to notice that at some point you're on an insecure HTTP URL. How did THAT happen, you'd ask yourself.

But didn't we write a bunch of code back in the day to force HTTPS?

Sure, but this still required that we ask the server where to go at least once, over HTTP...and every subsequent time, user keeps going to an insecure page and then redirecting.

HSTS is a way of saying "seriously, stay on HTTPS for this amount of time (like weeks). If anyone says otherwise, do an Internal Redirect and be secure anyway."

Some websites and blogs say that to implement this in IIS7+ you should just add the CustomHeader require for HSTS like this in your web.config. This is NOT correct:

<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security" value="max-age=31536000"/>
</customHeaders>
</httpProtocol>
</system.webServer>

This isn't technically to spec. The problem here is that you're sending the header ALWAYS even when you're not under HTTPS.

The HSTS (RFC6797) spec says

An HTTP host declares itself an HSTS Host by issuing to UAs (User Agents) an HSTS Policy, which is represented by and conveyed via the
Strict-Transport-Security HTTP response header field over secure transport (e.g., TLS).

You shouldn't send Strict-Transport-Security over HTTP, just HTTPS. Send it when they can trust you.

Instead, redirect folks to a secure version of your canonical URL, then send Strict-Transport-Security. Here is a great answer on StackOverflow from Doug Wilson.

Note the first rule directs to a secure location from insecure one. The second one adds the HTTP header for Strict-Transport-Security. The only thing I might change would be to formally canonicalize the www. prefix versus a naked domain.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS redirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}"
redirectType="Permanent" />
</rule>
</rules>
<outboundRules>
<rule name="Add Strict-Transport-Security when HTTPS" enabled="true">
<match serverVariable="RESPONSE_Strict_Transport_Security"
pattern=".*" />
<conditions>
<add input="{HTTPS}" pattern="on" ignoreCase="true" />
</conditions>
<action type="Rewrite" value="max-age=31536000" />
</rule>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>

Note also that HTTP Strict Transport Security is coming to IE and Microsoft Edge as well, so it's an important piece of technology to understand.

What was happening with my old (inherited) website? Well, someone years ago wanted to make sure a specific endpoint/page on the site was served under HTTPS, so they wrote some code to do just that. No problem, right? Turns out they also added an else that effectively forced everyone to HTTP, rather than just using the current/inherited protocol.

This was a problem when Strict-Transport-Security was turned on at the root level for the entire domain. Now folks would show up on the site and get this interaction:

  • GET http://foo/web
  • 301 to http://foo/web/ (canonical ending slash)
  • 307 to https://foo/web/ (redirect with method, in other words, internally redirect to secure and keep using the same verb (GET or POST))
  • 301 to http://foo/web (internal else that was dumb and legacy)
  • rinse, repeat

What's the lesson here? A configuration change that turned this feature on at the domain level of course affected all sub-directories and apps, including our legacy one. Our legacy app wasn't ready.

Be sure to implement HTTP Strict Transport Security (HSTS) on all your sites, but be sure to test and KNOW YOUR REDIRECTS.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
June 06, 2015 12:02
I think it is valid to send the HSTS header over HTTP, the browser is required to ignore that. From section 8.1:

If an HTTP response is received over insecure transport, the UA MUST ignore any present STS header field(s).


This is also the case when you do have HTTPS but not a valid certificate. So, there shouldn't really be a downside to sending the HSTS header anyway - we keep it in DEV and TEST environments, and it does no harm.

I'd also mention the HSTS Preload List, which is a way to hardcode your URL in the browsers, so even the first visit is redirected. A user may type example.com, and the default would be HTTP withut the preload list. I'm not sure how that one will scale.

By the way, I like HSTS because it got me out of encrypting the password in JavaScript - which is a weird idea to programmers, but it's hard to tell your management that you shouldn't encrypt something...
June 06, 2015 12:42
I have a question, I am getting Bad Request 400, Invalid Verb Error for one of my legacy system http://stackoverflow.com/questions/28061781/http-error-400-the-request-verb-is-invalid-in-iis-8-5. So it has to do something with the above error?
June 06, 2015 13:32
Well done! Do you also have control over https://profile.create.msdn.com/Register.aspx (redirected to after hitting the Sign In button on http://xbox.create.msdn.com/en-US/) to fix it there too? :<

I've tried to register as Xbox 360 Indie Developer (through DreamSpark) over three years ago and every time I sign in with my Microsoft Account on this particular page I get an redirect loop just as you described here. I've already contacted the Microsoft and Xbox support but nobody could help me. (They said I should create a new account and use that instead.) I'm pretty sure I'm not the only one who's affected by this problem.

Cheers!
June 06, 2015 21:57
@Kobi

The RFC says "An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.", so no, it is not valid per the spec.

The outbound rewrite rule provided will work correctly over either HTTP or HTTPS. In IIS I tend to set that rule at the server level. I only add the HTTPS redirect rule to sites that support HTTPS.
June 06, 2015 23:58
Knarfalingus - Thanks for the correction.
June 07, 2015 1:36
Knarfalingus, While the RFC seems to be obvious here, Kobi made a valid point as well. It's the browsers to sort things out and since it's all about to tell browsers, 'hey, I'm going HTTPS only', it's safe to include the STS header at the server level without outbound rewriting hassle if the 301 to HTTPS is set anyway. Finally, outbound rules are not available to most customers in shared hosting scenarios so applying the STS header via Web.config -> httpProtocol/customHeaders is the only option available here.
June 08, 2015 2:19
How do I change the above so it doesn't apply on localhost? I would like to have it disabled for localhost and enabled for anything else...
June 08, 2015 11:47
Any chance you can decorate the second rewrite rule with pseudo/comments. My brain doesn't get it.
June 08, 2015 12:07
I would recommend using NWebSec to apply HSTS. See this blog post explaining how to use it and also apply other HTTP headers used for better web security.

NWebSec also makes applying Content Security Policy (CSP) HTTP headers (Stops XSS attacks) very easy. Read more here.
June 08, 2015 14:57
It's recommmended to always add "includesubdomains" in the HSTS header.
June 09, 2015 17:33
Turns out they also added an else that effectively forced everyone to HTTP, rather than just using the current/inherited protocol.


I've been told to do this for SEO reasons in the past. I'm of the opinion that the search engines are clever enough to realise that both addresses are the same page, but SEOs tend to be a bit paranoid about duplicate content.

One definitely valid reason to do it is if there is a chance that there will be non-secure elements embedded in the page (possibly in WYSIWYG-managed or user generated content). If those pages are accessed securely, they will show needless mixed content warnings.
June 12, 2015 4:05
You could also do this with a HTTP Module. Although I prefer IIS to do the work as you described.
June 20, 2015 1:23
Hey Scott, I really like your Friday AZURE discussions and with you the star this week I thought I would comment. I couldn't find how to log a comment authenticating with live.com. Lots of social sites but I don't use most of them and none listed on the Channel 9 page. So hence this post of the blog. Perhaps make it more obvious to authentic with MS along with the others. In any case, really like these short, useful snippets and HTTP today in particular. Will try a bunch of stuff now. Have you heard of SQR (which is written in C) Will try it this way.
Thanks again.

John
June 21, 2015 19:36
Heh, I know which website that is Scott :)
September 03, 2015 0:29
Hi,
I just wanted to note that redirecting a user to the secure version of a site is vulnerable to a man in the middle attack since the first request is unsecure and the attacker can redirect them to a malicious site.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.