In the last week or so (July 2018) Chrome 68 has been released which introduces the Not Secure warning to the address bar of websites not using HTTPS. This is another step towards serving all websites over encrypted HTTPS. Troy Hunt and Scott Helme have both talked extensively about why we should be securing web traffic and using HTTPS even for simple static websites. Fortunately, many websites are getting the idea and are now securing their content with TLS certificates, however it is important to know that having HTTPS on your website doesn't mean it is immune from attack and more can be done to improve your website security. I recently watched a talk Scott Helme did back in 2016 at OSWAP London, where he outlined the advantages of including additional HTTP security headers to increase your website security and demonstrated how they help alleviate the chance of man-in-the-middle (MITM) and cross-site scripting.

TL;DR

I am going to configure additional security headers for this website which is hosted on Azure App Service with Azure CDN. We are going to use SecurityHeaders.com to assess and rate the headings on the website. Below are links to the sections.

What am I going to do?

I am going to use this website to assist me and any others in learning about security headers. I will gradually add more security headers to the website and document how I did so. You can find out more about the importance of security headers on SecurityHeaders.com (about page) which was created by Scott Helme. SecurityHeaders.com also provides us with a rating system to how well a website's security headers are configured, I will be analysing this website after every additional header, hopefully we will get to A+.

It is important to note this website is fairly basic and is just a bunch of static files (css and js are not even minified). The site is hosted on Azure App Service and utilise Azure CDN with a custom domain. That means the web server in use is IIS so we will be configuring the headers for an IIS server via a web.config file. The whole site including the web.config file can be found on GitHub at TheYorkshireDev/https-security-headers

I am NOT an expert in this field, I am learning as I go along so if you see an error please correct me and create a GitHub issue in the repository

Why a website and not a blog post?

I decided to create a website to document and demonstrate how to add additional security headers rather than a blog post so we had a practical example. In this website I have included several commonly used features websites have which are less trivial than some of the examples on the web. This site makes use of several different JavaScript/CSS CDNs, Google Analytics, Disqus commenting, external fonts and locally developed JavaScript/CSS. You can see the entire source-code on GitHub at TheYorkshireDev/https-security-headers

First analysis

As it stands, a brand new Azure App Service website is give a HTTPS endpoint but traffic to the website is not automatically redirected to HTTPS. Since that is the case I will start by analysing the website on SecurityHeaders.com using the HTTP endpoint. The results of which don't make good reading.

As mentioned above we are explicitly passing in the HTTP reference and checking follow redirects

The results returned are awful, an F. As you can see there are six security headers in red indicating they are missing from the response. We will go through them one-by-one and add the necessary configuration to web.config to utilise them. You may have noticed too the grade we can achieve is capped at A rather than A+, that is because we have some warnings and suggestions (see below).

There is a single warning Site is using HTTP and two additional headers that it is good practice to remove Server and X-Powered-By. Once these are addressed we should not be capped at an A rating on SecurityHeaders.com

It is clear from the first analysis what needs to be done to improve the website's security and rating. We need to make the following tweaks:

Warnings and Suggestions

The first analysis produced a warning that the HTTP endpoint of the website does not redirect to HTTPS, it also outlined the good practice of removing/changing the Server and X-Powered-By headers. In order to do this we first need to create a web.config file.

Change Server Version

To change the server response header and obfuscate the true server type, in the web.config file we need the following line:

<requestFiltering removeServerHeader="true" />

Remove X-Powered-By Header

To remove the X-Powered-By response header, in the web.config file we need the following line:

<remove name="X-Powered-By"/>

Remove X-AspNet-Version Header

To remove the X-AspNet-Version response header which isn't highlighted on the analysis because it is an ISS header only, in the web.config file we need the following line:

<httpRuntime enableVersionHeader="false"/>

Redirect HTTP to HTTPS

To redirect HTTP traffic to HTTPS automatically we need more than a single line in the web.config. We add a rewrite rule that will send all traffic to HTTPS with a 301 redirect.

<rule name="Redirect to https">
  <match url="(.*)"/>
  <conditions>
    <add input="{HTTPS}" pattern="Off"/>
    <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
  </conditions>
  <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
</rule>

Review

The complete web.config file for removing the warnings is below.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false"/>
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By"/>
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <rules>
        <rule name="Redirect to https">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Outcome

Now we have added the initial web.config, I have re-analysed the website with the results below.

As you can see the rating itself has not improved and is still F, however you may notice we are no longer capped at an A grade because we have addressed the warnings. SecurityHeaders.com also detects the update and removal of the Server and X-Powered-By headers.

X-Frame-Options

The X-Frame-Options header, protects visitors to the site from clickjacking. This headers allows you to lockdown the use of your website through i-frames, thus limiting the ability for an attacker to display your website such on their potentially malicious website. If you want to read more about clickjacking attacks Troy Hunt has a good blog post on Clickjack attack – the hidden threat right in front of you. The X-Frame-Options header has three possible values:

There is no reason for framing this website so I am going to add the DENY rule as a custom header with the following line in the web.config:

<add name="X-Frame-Options" value="DENY" />

Review

The updated web.config file after adding the X-Frame-Options header is below.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false"/>
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <add name="X-Frame-Options" value="DENY" />
        <remove name="X-Powered-By"/>
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <rules>
        <rule name="Redirect to https">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Outcome

Now we have added X-Frame-Options to web.config, I have re-analysed the website with the results below.

As you can see the rating itself has improved slightly from F to D and you can see the X-Frame-Options header is been detected.

X-XSS-Protection

The X-Xss-Protection header turns on the reflective XSS protection feature built into several browsers such as Internet Explorer, Chrome and Safari. There are three valid settings for this header:

We should utilise reflective XSS protection if it is available to us including enabling the blocking of responses if an attack is detected, so I am going to add the 1; mode=block rule as a custom header with the following line in the web.config:

<add name="X-XSS-Protection" value="1; mode=block" />

Review

The updated web.config file after adding the X-XSS-Protection header is below.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false"/>
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <add name="X-Frame-Options" value="DENY" />
        <add name="X-XSS-Protection" value="1; mode=block" />
        <remove name="X-Powered-By"/>
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <rules>
        <rule name="Redirect to https">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Outcome

Now we have added X-XSS-Protection to web.config, I have re-analysed the website with the results below.

As you can see the rating itself has stayed the same as D but the X-XSS-Protection has been added.

X-Content-Type-Options

The X-Content-Type-Options header instructs Google Chrome and Internet Explorer to stop trying to mine-sniff the content-type of a response rather than using the declared from the server. This header only has one possible value nosniff so is easy to add by including the the following line in the web.config:

<add name="X-Content-Type-Options" value="nosniff" />

Review

The updated web.config file after adding the X-Content-Type-Options header is below.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false"/>
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="X-Frame-Options" value="DENY" />
        <add name="X-XSS-Protection" value="1; mode=block" />
        <remove name="X-Powered-By"/>
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <rules>
        <rule name="Redirect to https">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Outcome

Now we have added X-Content-Type-Options to web.config, I have re-analysed the website with the results below.

As you can see the rating itself has improved from D to C and the X-Content-Type-Options header is been detected.

Strict-Transport-Security

The Strict-Transport-Security header enables HTTP Strict Transport Security (HSTS) between a web server and the browser. I am not going to attempt to explain the ins and outs of HSTS when Scott Helme has done so excellently in his blog post: HSTS - The missing link in Transport Layer Security. Essentially, HSTS enforces the use of TLS when navigating to a website preventing a user going to the http:// endpoint, thus mitigating the chance of a man in the middle (MiTM) attack further.

Implementing HSTS

There are three directives to the Strict-Transport-Security header:

We should utilise HSTS, and since this website is brand spanking new all endpoints should be secured with https:// therefore I am going to be setting the max-age to a high number. I have not preloaded this website to the preload lists so will not be using that directive.

It should be noted that best practice for enabling HSTS is to increase the max-age incrementally while you gain confidence that adding the header does not break any http:// endpoints you may have missed.

To include the Strict-Transport-Security header we need to add an outbound rule in the web.config:

<outboundRules>
  <rule name="Add HSTS Header" 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>
</outboundRules>

Review

The updated web.config file after adding the Strict-Transport-Security header is below.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false"/>
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="X-Frame-Options" value="DENY" />
        <add name="X-XSS-Protection" value="1; mode=block" />
        <remove name="X-Powered-By"/>
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <rules>
        <rule name="Redirect to https">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
      <outboundRules>
        <rule name="Add HSTS Header" 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>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

Outcome 1

I heeded my own advice on not setting the max-age too high while testing, so for the first analysis I set the max age to 10 minutes (600 seconds) and the results are below.

As you can see the rating itself has stayed the same C and the Strict-Transport-Security header is been detected albeit with the warning that the max-age is too low.

Outcome 2

After increasing the max-age to 31536000 (365 days) I then re-analysed the website with the results below.

As you can see the rating itself has improved from C to B and the Strict-Transport-Security header is been detected with appropriate values.

Content-Security-Policy

The Content-Security-Policy (CSP) header defines a whitelist of resource locations the browser should use when loading the website. CSP goes one step further, also specifying the resource type which can be loaded from the endpoint. In real terms it prevents any unauthorized scripts loading from your website assisting in mitigating the chance of cross site scripting (XSS) attacks for users on the website. For further information about CSP I recommend reading over Scott Helme's blog post on it: Content Security Policy - An Introduction.

A Report Only Mode?

You may spot some of the difficulties in trying to implement CSP straight away because if you get something wrong it can fundamentally break your website. Fortunately one of the recommend precursor to implementing a CSP header is to use the Content-Security-Policy-Report-Only header. This header works in the same way as the Content-Security-Policy header only instead of blocking resources that do not meed the defined policy a report will be generated. This report is sent to the website author allowing them to detect volitions and refine the policy before setting the actual CSP header. that will not block any of the resources but report violations.

As mentioned above when violations occur on either of the CSP headers, reports can be generated and sent to the website's authors. This allows for debugging into incorrectly set policies and/or attempts to link malicious scripts on your website. Fortunately Scott Helme (yup that guy again) has created https://report-uri.com a website that will receive your CSP reports and send you notifications if/when they occur.

Report Only Policy

To assist in creating the correct policy for this website I am going to turn on report-only mode and tell the browser to report all resources which are loaded. To do this we add the Content-Security-Policy-Report-Only custom header with the following line in the web.config:

<add name="Content-Security-Policy-Report-Only" value="default-src 'none'; report-uri https://xxxxx.report-uri.com/r/d/csp/reportOnly" />
Where the xxxxx is your custom report-url address.

Referrer-Policy

I have not implemented this header just yet.

Feature-Policy

I have not implemented this header just yet.

Useful links/sources


To be notified of the latest developments to the website and its security headers follow me on twitter.

Follow @TheYorkshireDev