So, while we’re all a under various social distancing/self isolation regimes in the plague times, somewhere in between binging Netflix shows and pinging the browser refresh button on the Covid-19 global dashboard there is a good time to learn about security headers.
The what and the why of these
Security headers are a core piece of configuration to keeping websites safe that most people don’t bother with, or get horribly wrong. That’s understandable. There’s a really short supply of solid documentation or approachable how-to guides or tools to help anyone figure them out. I’ve read a bunch of what’s available online for you, and still, even configuring this site’s headers felt more like voodoo than logic or common sense.
Security headers are code in your website that instructs a visitor’s browser on how to behave itself when visiting. They set out some rules on what the browser can and can’t do, with the aim of reducing the chance of them getting hit with some of your more run-of-the-mill attacks. That said, security headers are — like everything else in the stack of technology that makes of the web world wide — buggy, difficult to configure and hard to maintain. There are fuzzy standards around them and even a fuzzier level of browser compatibility. It’s a fantastic notion in concept, and pretty iffy on delivery.
Security headers can either white-list or blacklist browser behaviour and the kinds of content or actions your site should be delivering a browser. If someone is trying to serve up something strange through your site, it will snitch on them to the user’s browser. The problem is getting the balance right.
- Blacklisting: One method is to keep your headers loose, and just block certain behavours or sources you know you don’t want. Here, you are forever chasing new methods of how people can get up to no good. You may just blacklist a few things, and so you’re trading security for a swishy, mashed up modern web.
- Whitelisting: The other method is to start by blocking everything, and then just approve the few sources or features you need. In this method, every time you want to embed something new or incorporate a web service, API or whatever from a new source, you will have to whitelist the domains and scripts and actions. This doesn’t keep up with how the web works. All our websites are now basically serving bits and pieces of other websites. You’re deciding it’s a bit better to be more bland and yet able to block more things easily. It’s good for small blogs such as this. You can just whitelist a few things, such as a Google font, or the ability to embed a Youtube video.
A rant: We can do better
In case I haven’t bitched enough about headers yet, here’s some more…
Security headers are treacherous. They can work against you as much as for you. And there’s huge variance on how well they’re detected or followed. There are a couple of good services to check if your website (or anyone’s) has security headers implemented, and how well:
But even these two will give different results for the same website. Sometimes they don’t detect headers if they don’t like how/where they’re stored. Amazon.com rates a ‘D’ on the Mozilla site and ‘F’ on the other one. It’s the same site!
There are few solid, readable, entry-level resources for website owners to actually implement security headers well. Web hosting companies could solve this by scanning a site and updating its headers on the admin’s behalf or through a GUI. It’s kind of ludicrous that needs to be coded at all if you’re using a commercial hosting provider.
Security headers should be a solved problem the way Let’s Encrypt essentially made adding https to a site fairly trivial. How it’s interpreted by browsers or scanners should be standard in the way Web Standards set a standard. But it’s not. While experimenting on my own blog’s headers I was able to disable huge areas of functionality very easily. I was able to block WordPress’ still fairly new Gutenberg WYSIWYG, cover overlay effects, CSS for embedded tweets, break my own site’s CSS and so forth. After a while I got a bit tired of scouring my blog’s theme for all the references to things I could white-list. At times the Mozilla tool would give the blog an “A” grade while the other one would still rate it as a “C”. One would detect some settings and the other wouldn’t. In the end, I decided to choose between either leaving some functionality broken, or accepting a lower level of security. I left some things broken. Deal.
There should be scriptable security headers. Something that can install, detect what you’re running on your website, whether you’re using nginx or apache, ping you a few questions, and then install the security headers for you. If at some point I have the free time, that’s a thing I might try to create. Or maybe not. Anyway, on with the show.
The how and the where
Now that we’ve settled the fact that configuring security headers is an irritating, joyless task, inducing swearing at screens bringing little satisfaction for anyone who wants more out of life than to see some other website rank their efforts somewhere between “F” and “A+”, let’s add some security headers.
There are a number of ways to include these in your website, and they kind of come down to what level of access you have on your server, your comfort level with using the terminal. If you’re running an Apache server, you can
Some security headers, like your Content Security Policy, can also be implemented straight into the website template itself, between
</head> if you don’t have full server access. It’s kind of a weak area to implement these things though, and the CSP is one of the most touchy and buggy of the policies that needs more tweaking than the rest before you have it right. But the two flavours of server we’re likely working with are Apache and Nginx. If you’re using an Apache server, you can set your security headers in your
.htacces file. If you’re running an Nginx server, then your security headers get added into the
nginx.conf file. Where and how you include these is important. Format matters. The good news is that most online guides on this topic won’t tell you that, so you get an opportunity to learn through trial and error and internalised rage.
So, my blog’s security headers are a mess, but both security header checking site’s rank them as an “A” so something’s working. I went overkill in some areas and then tested and re-retested until the site vaguely worked again. I still haven’t white-listed all the Twitter scripts needed to make embedded tweets look right again, and there are some font and style elements in this blog’s WordPress theme, “Libre 2” that haven’t come back to life. If this was a paying gig, I might have persisted in the debugging.
This blog’s security headers entry in the nginx.conf file is based on this example. But almost every entry was changed to make it work more or less properly here. And then I also cheated. More on that in a bit.
In Nginx, you’ll need to edit the nginx.conf file. In your terminal, once you ssh into your server, if it’s anything like mine (which it probably is) then done with either sudo nano or vim straight from your own directory, here:
sudo nano ../../etc/nginx/nginx.conf
There are other settings in this file we aren’t going to mess with. Toward the bottom of the file, (before the final
} which closes the whole file) here’s my security headers section:
# ----------------------------------------------------- # SECURITY HEADERS - https://securityheaders.io/ # ----------------------------------------------------- # X-Frame-Options protects visitors against Clickjacking attacks. # Using iframe, the content of your site could be loaded inside another website. # See: https://www.webarxsecurity.com/https-security-headers-wp/ add_header X-Frame-Options "SAMEORIGIN" always; # Protects against Clickjacking attacks. # See: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet add_header Strict-Transport-Security max-age=31536000; # Protects against XSS injections. # See: https://www.veracode.com/blog/2014/03/guidelines-for-setting-security-headers/ add_header X-Xss-Protection "1; mode=block" always; # Content Security Policy (CSP). Prevent XSS, clickjacking, code injection attacks by implementing the Content Security Policy (CSP) header in your web page HTTP response. CSP instruct browser to load allowed content to load on the website. # See: https://geekflare.com/http-header-implementation/#Content-Security-Policy # I gave up and used a plugin in the end. See: https://wordpress.org/plugins/content-security-policy-pro # Prevents from leaking referrer data over insecure connections. # see: https://scotthelme.co.uk/a-new-security-header-referrer-policy/ add_header Referrer-Policy 'strict-origin'; # See: https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy # This MUST come AFTER the lines that includes .../sites-enabled/*, otherwise SSLv3 support may be re-enabled accidentally. include perfect-forward-secrecy.conf; # See: https://geekflare.com/http-header-implementation/#Feature-Policy # This one control a browser’s features such as geolocation, fullscreen, speaker, USB, autoplay, speaker, vibrate, microphone, payment, vr, etc. to enable or disable within a web application. add_header Feature-Policy "geolocation 'none'; camera 'none'; speaker 'none';"; # See: https://stackoverflow.com/questions/43447490/how-to-set-referrer-policy-with-nginx add_header 'Referrer-Policy' 'origin';
Whenever you make a change to this file on your Nginx server, you need to save it, and then run
service nginx reload to see the changes it may or may not have made on your website.
I have no idea whether I’ll maintain updated security headers on this site or allow it to slowly decline to keep pace with the managed decay of everything else in the world, but if this site was more than a hobby horse, then it would be essential.