Achieving an A+ Security Score for your Website
Security has always been important for anyone operating a website, but with the introduction of the GDPR and the very real threat (and reality) of astronomical fines, it feels like the stakes have been raised. It’s never been more important to take all the steps available to you to protect your website and your users' data.
There have been loads of new standards and techniques introduced over the past few years which are now widely adopted by the latest browsers, so it really makes sense for web developers and site owners to be making use of them.
Is my site secure?
So the first thing to do is to find out how things stand currently and what can be improved, by running your site through a few online audit tools. There are a number of great resources out there to point you in the right direction.
A good place to start your quest for a more secure site, is the Mozilla Observatory (https://observatory.mozilla.org). I love this tool, but it can be a little harsh as it’s applying the latest standards so don’t get disheartened if you get an ‘F’ score initially - it doesn’t mean your site is super insecure, it just means that there is more you can do to reduce the chance of hackers gaining access.
This tool was invaluable when I was improving the security of our site (18aproductions.co.uk). Every site has it’s own set of challenges when it comes to optimising security - the platform it’s built on, the hosting environment, the PHP version, and the webserver technology all play a part. I’ll go through the main obstacles I came up against and how I delt with them below
Content Security Policy
The top of the Mozilla checklist is implementing a Content Security Policy (CSP) for your site. This is basically a list which tells your browser what content is allowed to run from where and is a great way of reducing the chance of cross-site scripting (XSS) on your website.
I won’t go into huge detail explaining how to create a content security policy here because there are already tools out there to help you such as https://developers.google.com/web/fundamentals/security/csp/. However it’s basically just a list of types of things and the URLs that are allowed to call them.
So I’ll assume at this point that you have a CSP and just need to apply it to your site.
Implementing a Content Security Policy on an existing website
Retro-fitting a CSP to an old website can be time-consuming, but there are a number of approaches you can take to make it less painful.
Our site was built over 10 years+ ago on CodeIgniter. I love CodeIgniter, but it’s not as up to date with the latest standards as other frameworks (such as Laravel), so there’s more you need to do yourself to improve security - you can’t just add a new package with composer and turn it on as with Laravel - at least not in my experience.
If you’re website is running on the Apache webserver with the headers module (https://httpd.apache.org/docs/current/mod/mod_headers.html), the quick and easy way to apply a CSP to your site is to just add a Header to your websites .htaccess file as per the below:
Nonces, Hashes and Meta tags
I’m already using a custom templating library with CodeIgniter (the site was built on a version of CodeIgniter before templating was built in as standard), so I added a new function called csp() so I could output the CSP on page load, based off a nice nested array defined in a new csp.php config file, then added in the nonce value and made it available in the template view. It’s pretty easy stuff, but here’s an example of my config file and associated function added to my templating library:
Here’s my new CodeIgniter config file (csp.php) which defines that I want in my policy.
And here’s the function added to the template library.
(FYI - $this->csp_nonce = base64_encode(time()); was defined in the __construct().)
So in my template view I can just call:
Disclaimer: This is by no means a complete solution and will probably not suit your needs - it’s just a quick solution which suits my specific needs and allows me to define a nonce alongside appling the policy defined in the config file via the <meta> tag. So I’m including here for illustrative purposes only.
I’m not 100% sure this is a perfect approach as I don’t really see why a hacker couldn’t look at the <meta> tag on the page to determine what to set the nonce for their injected JS code, but this approach seems to satisfy both my browser and the Mozilla testing tool (securityheaders doesn’t seem to recognise a CSP defined as a Meta tag - I guess it’s only looking at headers after all). So hopefully this is all good (comments welcome?). If nothing else I figure it makes life a little harder for anyone trying to cause trouble and that’s what it’s all about after all.
Our site is pretty straight forward and small really, as soon as you’re implementing a CSP on a larger, more complex site your problems increase, so I’d suggest reading up a little more on the topic. https://wiki.mozilla.org/Security/CSP/Specification
Setting ‘Samesite’ Cookies with CodeIgniter
‘Samesite’ cookies are a pretty new thing. Infact, you need the latest version of php (7.3) in order to set this new option in the setcookie() function (https://www.php.net/manual/en/function.setcookie.php). However the lack of these was really hurting my Observatory score so I needed to do something about it.
I was running php 7.2 on my EC2 instance (webserver), so the first challenge was upgrading php which I won’t go into here, but suffice to say - I got it done.
The next problem was that CodeIgniter didn’t have support for this feature in it’s system classes. So there was more work to be done.
After a little investigative work I found it wasn’t too big a deal, I just had to extend the core/Session and core/Security classes and the library/Session/Session classes with versions of my own using the MY_ approach (https://codeigniter.com/user_guide/general/core_classes.html and https://codeigniter.com/user_guide/general/creating_libraries.html)
I won’t post all the code here, but the general gist was to pass an array of options to the setcookie() function, including a samesite value (either Lax or Strict) (defined in the main config file):
Setting secure headers for your website
Another really cool online tool is https://securityheaders.com. Using this tool I was able to check if I was passing all the correct headers I needed to, including:
Again, using Apache you can define many of these in your .htaccess file, for example:
So that’s all pretty easy peasy and gives your score an easy boost.
Cross Site Request Forgery (CSRF) tokens
One thing you can just ‘turn on’ in CodeIgntier are using Cross Site Request Forgery (CSRF) tokens - Just set:
in your config file. If you have specific URLs you want to exclude, you can add these to your $config['csrf_exclude_uris'] = array();
One thing to watch out for here is that you’re using the form_open() helper on all your forms, if you are it’ll add in your token for you as a hidden field. If you’re just manually opening forms, it won’t and your form submissions will fail.
The other gotcha with CSRF tokens I’ve found is where you send POST requests to your site using jQuery AJAX. You’ll need to manually send the token and value to the backend for checking with PHP. Or the quick solution is to change to GET requests instead as these don’t require the token to be set.
In theory Subresource integrity shouldn’t have been too difficult to implement, however as we pull our assets off a CDN (cloudfront), subresource integrity hashes are required on these resources.
It’s easy enough to grab these from the SRI hash generator tool https://www.srihash.org/ however our site also uses an asset number in order to invalidate the cache when we make a change to the code. I.e. We append a version number to the end, for example:
Not only that, but we have real-time minification of the JS and CSS of the exact URLs pulled in on each page and created ‘on the fly’ as the page loads.
So this would not only mean generating a new hash for every different variation of URL we’re pulling from cloudfront, but also updating every single one when we updated the asset number. Which would make it all completely impractical.
So my solution was to find out how the SRI hashes are generated and make my own, then apply it in realtime via the Template class as with the content security policy.
Thanks to this article https://tenzer.dk/generating-subresource-integrity-checksums/ that wasn’t too difficult to do. Here’s an example of the new function I’ve added to the Template class:
To try and speed up the process I also cache, to a text file, the hash generated from a given resource URL to make retrieval slightly quicker on subsequent requests.
This could be done much better (for example by loading up all the hashes required in a single request), however for my very simple purposes, this does the job and allows the site to apply subresource integrity without losing too much speed.
I can then use the following in the template view.
Checking SSL Security
I’ve written about this before, but another important check you should run is the SSL certificate and its implementation on your server.
There are many online tools out there but I like SSLLabs https://www.ssllabs.com/ssltest/analyze.html?d=www.18aproductions.co.uk
This should point you in the right direction to check your web server and certificate are setup correctly. Many of the things it suggests can be implemented at the server configuration level which is easy enough to do with Apache and there are plenty of online guides out there. In my case it was just a matter of going through this guide https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/SSL-on-amazon-linux-2.html
So after many, many hours and many more coffees later, I’d managed to boost my score right up to an A+ and Mozilla had no further advice for me https://observatory.mozilla.org/analyze/www.18aproductions.co.uk, which I’m pretty chuffed about. I expect by the time you read this another new acronym / standard will have been introduced and I’ll need to re-write this article, but for the moment I’m enjoying my moment at the top! :)
So if you want to make sure you’re doing all you can to protect your site and your users from hackers, please do give us a shout and we’ll run a few checks for you to see what can be done. Every site is unique and presents it’s own set of challenges, but there may be some easy wins we can implement relatively quickly.
Want to share? Tweet it!