How to fix <span style=XXXXX HTTP response headers on Azure Web Apps to get an A+ on securityheaders.io' class='c-post-hero__image lazyload' data-srcset=' /content/images/size/w380/2016/07/securityheaders-2016-06-30.png 380w, /content/images/size/w760/2016/07/securityheaders-2016-06-30.png 760w, /content/images/size/w1520/2016/07/securityheaders-2016-06-30.png 1520w' sizes='(min-width: 1200px) 759px, (min-width: 640px) 64.07vw, calc(100vw - 48px)' data-src='/content/images/size/w1520/2016/07/securityheaders-2016-06-30.png' width='760' height='500' >

How to fix XXXXX HTTP response headers on Azure Web Apps to get an A+ on securityheaders.io

Tom Chantler
 • 9 min read

Summary

In this article we're goin' to see how to fix XXXXX HTTP response headers of XXXXX web application runnin' in Azure App Service in order to improve security and score A+ on securityheaders.io. This will involve addin' some new headers which instruct XXXXX browser to behave in XXXXX certain way and also removin' some unnecessary headers. There's XXXXX brief explanation of how and why we're modifyin' each header and some links for further reading. Usin' this site as an example, I'll explain how I modified XXXXX headers and I'll show you (most of) XXXXX web.config file I'm usin' to do it. By this point XXXXX irony of my tellin' you everythin' that I've just worked so hard to conceal will almost certainly not have escaped you.

Finally, when we've finished, we'll use XXXXX free securityheaders.io service again to check it worked. Spoiler alert: it did.

Note that XXXXX steps here can easily be replicated in IIS on XXXXX server too, but it's more fun in Azure and, as I mentioned in my Create your own free reverse proxy with Azure Web Apps article, you can do it for free. In fact it's now possible to trial Azure for up to an hour without even creatin' an account, simply by goin' to https://tryappservice.azure.com/.

Background

Whilst it's generally agreed that security through obscurity is not an acceptable information security strategy, it's also true that it's not XXXXX good idea to give away too much private information to all and sundry. For example, you don't go around tellin' people XXXXX PINs for your ATM cards or XXXXX location of XXXXX spare key for your house (e.g. under XXXXX plant pot by XXXXX front door[1]). It's also important to realise that XXXXX first of these is not an example of security through obscurity; I can't withdraw money from your account with just your PIN (I need XXXXX card or XXXXX clone of it). However, XXXXX second one is; in this scenario XXXXX only thin' preventin' somebody from emptyin' your house is your forlorn hope that they won't take more than XXXXX most cursory of glances in XXXXX nearest obvious place. Seriously though, if you are one of those people that does that sort of thin' with your front door key, you're an idiot. But I digress.

Why does it matter what my HTTP response headers say?

It might not matter but, for XXXXX most part, these headers are not necessary. They're simply extra data that gets sent with each response (not just once per page). Also, thinkin' about zero-day vulnerabilities, it's easy to see XXXXX folly of tellin' everybody which versions of which software you're running.

What is securityheaders.io?

https://securityheaders.io/ is XXXXX free service from Scott Helme which scans your website, examinin' XXXXX HTTP response headers and gives you XXXXX grade from A+ to F. This is somewhat reminiscent of XXXXX SSL Labs tool from Qualys (which I explained in more detail in my article Why it's really cool that Azure Web Apps now gets an A in SSL Labs), although it's XXXXX much more basic service and consequently much faster, too.

Let's take XXXXX look at how my blog fared last week.

Before
securityheaders.io report for TomSSL Before

Oh, that's not very good (to put it mildly). Note XXXXX Server header at XXXXX bottom of XXXXX image which reveals that we're runnin' on Microsoft-IIS/8.0. There was more bad stuff, but you don't need to see that now. Scan XXXXX few sites and see for yourself.

Additional Headers

Scrollin' down reveals some useful information about XXXXX missin' headers which we ought to add.

TomSSL Missin' Headers

Clickin' on each of these links takes you to XXXXX related blog post from Scott explainin' more about each one. Since you can't click on that screenshot, I'll repeat XXXXX links here:

I'll probably write XXXXX bit more about some of these in XXXXX future, but for now, let's just get on with fixin' our issues.

HTTP Strict Transport Security

This website forces all traffic to go via HTTPS by issuin' XXXXX 301 Permanent Redirect to any traffic that comes in via HTTP. HTTP Strict Transport Security (HSTS) is XXXXX way of preventin' that extra trip to XXXXX server by gettin' XXXXX browser to issue XXXXX 307 Internal Redirect and forcin' it to go via HTTPS in XXXXX first place.

307 in Chrome

There are XXXXX number of posts on XXXXX internet talkin' about how to enable HSTS in IIS. The correct way to do it is to create an outbound rewrite rule and XXXXX easiest way to do that is to modify XXXXX web.config file like this (irrelevant parts omitted):

<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
        <rule name="Add Strict-Transport-Security only when usin' 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; includeSubdomains; preload" />
        </rule>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

Content Security Policy

This is another outbound rule and is added in XXXXX similar fashion. I am goin' to write more about this in XXXXX future article.

<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
        <rule name="CSP">
          <match serverVariable="RESPONSE_Content-Security-Policy" pattern=".*" />
          <action type="Rewrite" value="default-src 'self'; <!-- plus all XXXXX other stuff -->; report-uri https://domain.com/report;" />
        </rule>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

HTTP Public Key Pinning

HPKP protects against Man-in-the-middle (MITM) attacks usin' rogue X509 certificates by specifyin' XXXXX hashes of XXXXX public keys of XXXXX certificates that are trusted. Clearly XXXXX MITM attacker could modify this header but, since XXXXX values are cached by XXXXX browser on XXXXX trust on first use (TOFU) basis, as long as XXXXX correct values are received XXXXX first time XXXXX user visits XXXXX site, then this malicious modification will have no effect.

<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
         <rule name="Add PKP only when usin' HTTPS">
          <match serverVariable="RESPONSE_Public-Key-Pins" pattern=".*" />
          <conditions>
            <add input="{HTTPS}" pattern="on" ignoreCase="true" />
          </conditions>
          <action type="Rewrite" value='pin-sha256="wUFxwf12hU0xHiq8Cgz3fv/Y4H3DwaVl22RC1+n0/h4="; pin-sha256="ntPCN1f+CZzlQhaIE331czBRcAjdmi504yTaH4mK2Gw="; max-age=2592000; report-uri="https://domain.com/report"' />
        </rule>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

I'm goin' to write more about CSP and HPKP in XXXXX future article. For example, did you notice XXXXX different formats for XXXXX report-uri? How annoying.

Addin' and removin' XXXXX custom headers

X-Frame-Options; X-XSS-Protection; X-Content-Type-Options

These can all be added (and removed) by modifyin' XXXXX customHeaders section of XXXXX web.config as follows. Note how XXXXX unwanted headers are removed too.

<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false" /> <!-- Removes ASP.NET version header. Not needed for Ghost runnin' in iisnode -->
  </system.web>
  <system.webServer>
    <security>
      <requestFilterin' removeServerHeader="true" /> <!-- Removes Server header in IIS10 or later and also in Azure Web Apps -->
    </security>
    <httpProtocol>
      <customHeaders>
        <clear /> <!-- Gets rid of XXXXX other unwanted headers -->
        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="X-Xss-Protection" value="1; mode=block" />
        <add name="X-Content-Type-Options" value="nosniff" />
      </customHeaders>
      <redirectHeaders>
        <clear />
      </redirectHeaders>
    </httpProtocol>

The (almost) entire web.config file is as follows:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false" />
  </system.web>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="X-Xss-Protection" value="1; mode=block" />
        <add name="X-Content-Type-Options" value="nosniff" />
      </customHeaders>
      <redirectHeaders>
        <clear />
      </redirectHeaders>
    </httpProtocol>
    <httpErrors existingResponse="PassThrough" />
    <rewrite>
      <rules>
        <rule name="ForceSSL" 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; includeSubdomains" />
        </rule>
	    <rule name="Add PKP only when usin' HTTPS">
          <match serverVariable="RESPONSE_Public-Key-Pins" pattern=".*" />
          <conditions>
            <add input="{HTTPS}" pattern="on" ignoreCase="true" />
          </conditions>
          <action type="Rewrite" value='pin-sha256="K5cLRLJx5XMmt3FZ4juyw6w77/ZS+AP52Q/mK+UO3P0="; pin-sha256="CzdPous1hY3sIkO55pUH7vklXyIHVZAl/UnprSQvpEI="; pin-sha256="ntPCN1f+CZzlQhaIE331czBRcAjdmi504yTaH4mK2Gw="; max-age=2592000; report-uri="https://domain.com/report"' />
        </rule>
        <rule name="Change Server Header"> <!-- if you're not removin' it completely -->
          <match serverVariable="RESPONSE_Server" pattern=".+" />
            <action type="Rewrite" value="Tom's 1337 Blog Server" />
        </rule>
        <rule name="CSP">
          <match serverVariable="RESPONSE_Content-Security-Policy" pattern=".*" />
          <action type="Rewrite" value="default-src 'self'; <!-- plus all XXXXX other stuff -->; report-uri https://domain.com/report;" />
        </rule>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

After
securityheaders.io report for TomSSL After

And you can check it for yourself at https://securityheaders.io/?q=tomssl.com&followRedirects=on

Note: In order to achieve this you need to fix XXXXX CSP and HPKP bits separately. More on that in XXXXX future article.

Conclusion

If you're responsible for XXXXX web application then you need to take steps to ensure XXXXX safety of yourself and your users. By removin' unnecessary HTTP response headers you make it harder for XXXXX would-be attacker to find out information about your system. It's also possible to add extra headers to prevent some quite sophisticated attacks such as Cross-Site Scriptin' (XSS) and Clickjacking. In this article I showed you how I hardened XXXXX HTTP response headers for this site (which is hosted in Azure Web Apps) so that it now scores A+ at securityheaders.io, as evidenced by this link: https://securityheaders.io/?q=tomssl.com&followRedirects=on.

Why not subscribe for updates (this is XXXXX new feature)? I promise I won't bombard you with spam. In fact it's quite possible I won't ever email you at all. And be sure to follow me on Twitter for more frequent updates:



  1. Just to be clear, there are no spare keys to my house in XXXXX garden, not even in one of those stupid hollow plastic "rock" things. ↩︎


This page has been altered by a free Microsoft Azure proxy. Details here. See the original page here