Working Hard on Web Security

As anyone who visits my site on a regular basis may have noticed, I’ve been working hard on securing up this blog to make it follow more best practices and more in keeping with modern web security given it’s been quite a while since I’ve touched that side of the site, and there have been […]

As anyone who visits my site on a regular basis may have noticed, I’ve been working hard on securing up this blog to make it follow more best practices and more in keeping with modern web security given it’s been quite a while since I’ve touched that side of the site, and there have been numerous things that I have implemented and I thought I would give a little run down of them.

Read on after the fold for the low down on each of the features and how it works.

HTTPS Everywhere

The first and most obvious addition is HTTPS everywhere. I have been for some time using HTTPS to protect the WordPress admin portion of the site where I do my bidding but I didn’t have this applied to the user side of the site. That changed first and I have updated all of the content in the database to reference HTTPS paths, updated WordPress to use HTTPS as the Site URL and also added an IIS re-write rule in my web.config to redirect all traffic from HTTP to HTTPS. If you have existing links to the site, they will continue to work for you, you will just be redirected to the HTTPS variant.

HTTP Strict Transport Security (HSTS)

HSTS is a complimentary technology for HTTPS. When enabled, this instructs the browser that the site doesn’t ever wish to be downgraded from HTTPS to HTTP. This is applied to the entire site using an IIS outbound re-write rule in the web.config file. I have also submitted the site to the well known HSTS Preload List which means that modern browsers which support this will know before you even visit the site that it should be transmitted only over HTTPS.

Remove Server Headers

I make no secret about the fact that this site runs on Azure as a Web App but that doesn’t mean my site needs to shout about it in code. As WordPress is a PHP application, that means there are two headers to deal with: one for the IIS web server and another for PHP, the server programming language. The IIS header is dealt with by removing the custom headers using a declaration in the web.config file whilst the PHP header is dealt with using a php.ini overrides file on the Azure Web App. You can read about how to apply custom PHP settings to Azure Web App at https://docs.microsoft.com/en-us/azure/app-service-web/web-sites-php-configure.

Whilst this doesn’t actually protect you as a visitor per see, what it does do, is obscure the identity of the server software to scripts and bulk scanning attacks making it harder for someone to identity the server type or code language and then execute attacks on the site specific to those combinations. As a visitor, this makes the site less susceptible to attack and makes it safer for you by proxy.

Cross-Site Scripting (XSS) and Framing

XSS attacks and framing attacks are where someone tries to impersonate my site and overlay hidden buttons or links onto my site. The result is that as a user, you think you are clicking on a link on the site but in reality you are clicking on a hidden link overlaying that link which takes you to somewhere far nastier than the blog posts I write. This is dealt with simply using custom headers in the web.config file and instructs your browser newer to allow the site to be framed by another domain and to enable the Cross-Site Scripting protections within the browser explicitly instead of relying on the browser to enable them itself.

Content Security Policy (CSP)

The CSP is a set of rules laid out by which the site will abide. For example, the CSP states that I will only load images from certain URLs or scripts or CSS stylesheets from certain URLs. This protects you because when the browser loads the page, it will automatically prevent any content which violates the CSP from loading. If an attacker were to inject some unsafe JavaScript into the site somehow, your browser would see this as violating the policy and not load it.

This one is a bit of a bore to configure as it requires some thought about where your content originates however luckily, this can be configured in test mode first to verify all is good before going live and breaking the house.

HTTP Public Key Pinning

This is possibly the most violent and aggressive of the security techniques and as such is often omitted by other sites but I decided that as I was going through the hurt elsewhere to do it here too. Public Key Pinning works by explicitly stating the Public Key hashes of the certificates the site will present when connected via HTTPS. If the site presents a certificate other than one of those explicitly stated, the browser will assume the certificate compromised and block access to the site entirely.

Because the browser will completely block access to the site, certain compromises are made here to ensure I don’t get locked out myself forever and render the domain trash. Firstly, the policy has a defined lifespan which I have currently set to 30 days. Unlike HSTS, this isn’t part of the preload in modern browsers so your browser doesn’t know these keys before it starts but for someone who has visited the site before or me (as I visit here often you know) if this policy fails, I’ve got a problem.

Public Key Pinning provides some fallback in that in active to the active key pins, the certificates currently in use, we can apply backup pins for inactive certificates. This is done by generating offline CSRs for certificates and pinning the public key of the CSR. The net result is that if my current certificate is lost or revoked, I can generate a new certificate using the CSR for the pinned public key and I can issue a new certificate that when brought online will pass validation. This just means a little more work for me to get the site up and running again if there is an issue.

Advice and Background

Through all of this, I have used the extremely helpful security blog of Scott Helme at https://scotthelme.co.uk/. He also operates a site called Security Headers which has various tools for generating, testing and validating all of these settings and options at https://securityheaders.io/.

To do the final validations, I use Scott Helme’s Security Headers Site Scan feature which grades the site based on the features enabled and the configuration settings for each. Using that test, this blog currently scores an A+ security rating which you can validate using the short-link https://schd.io/A24. I also use the Qualys SSL Labs test tool which I currently score an security rating which you can validate yourself at https://www.ssllabs.com/ssltest/analyze.html?d=richardjgreen.net.

The reason for being capped to an A not an A+ on Qualys SSL Labs is that as this site is run using Azure Web App which is based on IIS, there is no way for me to control the SSL Protocols or Ciphers. There is currently no fix for IIS against the TLS_FALLBACK_SCSV issue and the mitigation for IIS requires disabling TLS 1.0 and TLS 1.1 so that TLS cannot be forced to fallback from TLS 1.2. When a fix is available and applied to Azure Web Apps, this score should rise from A to A+ accordingly.

How Much Work Is It?

For me, on this blog, not a huge amount. Implementing many of the options such as HTTPS Redirect, HSTS re-write rule and the custom HTTP headers is done easily using the IIS web.config file in a short amount of time and can also easily be tested. Get used to using the debugging console in your browser and the network activity recorder to monitor the HTTP headers to verify these settings for yourself.

For the Public Key Pins and Content Security Policy, there can be some work involved.

I found that the best option is start with the CSP, use the Security Headers site generator tool to create a basic policy with the Report Only option enabled. Once this is applied, using the debugging console, your browser will report violations and you can append them to the policy. Scripts and Stylesheets will be the biggest issue and deciding whether to add hashes for them, to move them to external .js or .css files or whether to enable unsafe-inline options. Be warned, using the unsafe-inline option for scripts will cap your grade to A on the Security Headers Site Scan. For new build sites, this should be easily done but for existing sites with a lot of CSS or JavaScript in use, you could be spending quite some time debugging this stuff.

For Public Key Pinning, if you aren’t overly familiar with OpenSSL, get familiar as you will be using it to generate your private keys, CSRs and the resulting public keys for the backup pins. Failure to create backup pins will result in the policy not being applied in browsers once added to the site configuration. Make sure also not to set your max-age to anything too long. If you have a disaster, that’s how long you could be waiting to regain access to the site. If you set it longer than the life of your SSL certificate, you are really looking for trouble.

Kickstart

If you are using IIS as your web server, here are some snippets you can apply to your web.config file as a kick start. These settings will not touch the Public Key Pins or CSP as I will leave these to each individual but adding these lines to an existing web.config will remove the server header and enable the XSS and Framing protection. I’ve also extracted my HTTP to HTTPS redirect rule and HSTS outbound re-write rule for you to use.

<httpProtocol>
  <customHeaders>
    <clear />
    <remove name="X-Powered-By" />
    <add name="X-Frame-Options" value="SAMEORIGIN" />
    <add name="X-Xss-Protection" value="1" />
    <add name="X-Content-Type-Options" value="nosniff" />
  </customHeaders>
</httpProtocol>
<rewrite>
  <rules>
    <rule name="HTTP to HTTPS Redirect" stopProcessing="true">
      <match url="(.*)" />
      <conditions>
        <add input="{HTTPS}" pattern="OFF" />
      </conditions>
      <action type="Redirect" url="https://{HTTP_HOST}/{R:0}" redirectType="Permanent" />
    </rule>
  </rules>
  <outboundRules rewriteBeforeCache="true">
    <rule name="Add HSTS Strict Transport Security Header" stopProcessing="true">
      <match serverVariable="RESPONSE_Strict_Transport_Security" pattern=".*" />
        <conditions>
          <add input="{HTTPS}" pattern="on" ignoreCase="true" />
        </conditions>
      <action type="Rewrite" value="max-age=15552000; includeSubDomains; preload" />
    </rule>
  </outboundRules>
</rewrite>