Or: how to make a website more secure using a few headers with securityheaders.io.
More secure in what sense? You ensure the page loads only over HTTPS. You reduce the risk of MitM (Man-in-the-middle) attacks, clickjacking, and XSS. You need to know which web server you're using. If it's nginx and you can edit nginx.conf, it's just a few lines you add to the file. If you have Apache or can't edit the nginx configuration you'll need to use a .htaccess file. You'll add all headers inside the following tags:
The X-XSS-Protection header sets the XSS Auditor. Option 1 enables it and when the auditor detects a Reflected XSS, the page simply won't load at all. Option 0 disables this.
Header set X-XSS-Protection "1; mode=block"
X-Frame-Options sets conditions for embedding content in an iframe. In my case I'm disabling it entirely. You'll also encounter the SAMEORIGIN option (i.e. same domain, protocol, and port).
Header always append X-Frame-Options DENY
X-Content-Type-Options tells the browser not to interpret a file's MIME type on its own, but to leave that to the web server. This prevents Chrome from wanting to run js inside a txt file, for example — Google apparently knows best.
Header set X-Content-Type-Options nosniff
Referrer-Policy specifies under what conditions the web server should pass along where you came from. It mainly applies to transitions from HTTPS to HTTP. In this case I'm not passing the referrer at all.
Header set Referrer-Policy: strict-origin
Feature-Policy controls the page's access to device resources. I'm genuinely not interested in location, microphone, accelerometer… hence none.
Header always set Feature-Policy "geolocation 'none'; accelerometer 'none'; gyroscope 'none'; picture-in-picture 'none'; microphone 'none'; payment 'none'; sync-xhr 'none; usb 'none'; vr 'none'"
Content-Security-Policy is a tricky header. It specifies which sources content can be loaded from — scripts, styles, images, fonts… Anything you don't specify won't load.
Header set Content-Security-Policy "default-src 'self' https://www.ondrejsramek.cz; script-src 'self' 'unsafe-inline' https://ondrejsramek.cz/user/plugins/admin/themes/grav/js/ https://www.google-analytics.com https://srameko.report-uri.io; style-src 'self' 'unsafe-inline' https://www.ondrejsramek.cz https://fonts.googleapis.com; img-src 'self' data: https://www.ondrejsramek.cz https://www.gravatar.com https://www.google-analytics.com https://stats.g.doubleclick.net; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://getgrav.org/; object-src 'none' ; upgrade-insecure-requests; referrer no-referrer-when-downgrade; report-uri https://srameko.report-uri.io/r/default/csp/enforce; report-to https://srameko.report-uri.io/r/default/csp/enforce"
Public-Key-Pins tells the browser what the hashes of the certificates in the trust chain are — not just yours, but also the root and intermediate. This covers the case where yours expires. If the hashes don't match, the connection won't be made.
Header set Public-Key-Pins 'pin-sha256="pWduCAbO1PxeBmY5QyD300XFYJhIr4xOein6xdQk038="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys="; includeSubdomains'
Strict-Transport-Security specifies the number of seconds for which the page will load over HTTPS. Even if you tried HTTP, the browser would force HTTPS.
Header set Strict-Transport-Security 'max-age=31536000; includeSubDomains'
What problems will you run into? Google Analytics. If you're using ga.js, note that it can be loaded over both HTTP and HTTPS. You'll need to add ssl.google-analytics.com and www.google-analytics.com to your CSP. Don't forget that Analytics also uses images:
script-src https://ssl.google-analytics.com https://www.google-analytics.com; img-src https://www.google-analytics.com https://stats.g.doubleclick.net;
Another complication is the use of inline <script> tags — you'll need to transform them by creating an external script file and loading it instead. Also start using SRI (Subresource Integrity). For scripts and styles loaded from a CDN you specify a hash. If it doesn't match when the page loads, it won't be used. The Bootstrap and Cloudflare CDNs will give you the tag directly with the integrity attribute included. For your own files use the tool on report-uri.io, which will calculate the hash for you. If your web server doesn't support CORS SRI don't use it — modern browsers (current Firefox, Chrome) wouldn't be able to verify the hash match. Get all of this right and securityheaders.io will reward you with an A+ rating.
