The essential no-excuses security checklist for modern websites.
Last update: Sep 22, 2020
The web and its security has come a long way. As always in security there are constantly ways to improve your defences agains bad actors. This is a list of quick and easy, yet powerful tools that all devs should be using.
Update 18 Sep 2019 @ 16:02
As the reddit user zfa suggested I included the link to the Mozilla Observatory for automatic auditing.
- Useful Libraries & Tools
Let’s start with the most obvious. It’s almost 2020 and websites not using HTTPS are simply being irresponsible. There is no reason to run plaintext http in the era of Letsencrypt where getting a certificate is easy and free. I won’t go over how to configure that, there are tons of resources out there and generally its as simple as adding a line in your config file.
Also redirect all the http traffic to https automatically, basic stuff.
2. TLS Versions
97.65% of global users support TLS version 1.2, so go disable anything below that in your server as it has knows vulnerabilities!
# Nginx ssl_protocols TLSv1.2;
# Apache SSLProtocol -all +TLSv1.2
# Traefik [entryPoints] [entryPoints.https] [entryPoints.https.tls] minVersion = "VersionTLS12"
Similar to the TLS version you should avoid using anything different than AES or ChaCha20. Limit the ciphers to something secure and modern.
# Nginx ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
# Apache SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLHonorCipherOrder on SSLCompression off SSLSessionTickets off
# Traefik [entryPoints] [entryPoints.https] [entryPoints.https.tls] cipherSuites = [ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", ]
Once your website runs on HTTPS it’s a good idea to tell the browser not to use HTTP anymore. Thats what the HTTP Strict Transport Security (HSTS) is designed for.
Strict-Transport-Security: max-age=31536000; includeSubDomains
This basically tells the browser to only talk to your domain via HTTPS for the next year. You can exclude the
includeSubDomains if you want to just target your root.
# Nginx add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Apache Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
5. Content Security Policy (CSP)
This awesome HTTP header is incredibly powerful! It allows you to specify the allowed origins for all kind of files that will be loaded into your website.
Awesome right? The header for that example would look like this:
Content-Security-Policy: "default-src 'self'; img-src https://unsplash.com/; font-src https://fonts.googleapis.com"
This allows a dev to specify exactly what resources are allowed to load from what domains. Don’t worry, you don’t need to remember the exact syntax:
Use the generator: https://www.cspisawesome.com/
This is a basic one, but it should not be forgotten. It prevents your website to be displayed inside another one. This prevents shit tons of possible attack vectors. Simply set the header and you’re done.
# Nginx add_header x-frame-options "SAMEORIGIN" always;
# Apache header always set x-frame-options "SAMEORIGIN"
Again, a little header that prevents lots of damage. By setting the Content Type header you prevent the browser from guessing what file contents a downloaded resource is. Basically if
/image.jpeg is actually a
.js file the browser would still run it if you don’t set the header
# Nginx add_header x-content-type-options "nosniff" always;
# Apache header always set x-content-type-options "nosniff"
Libraries & Tools
For some automatic auditing of your website you can use the excellent Mozilla Observatory tool. You can scan a domain and receive information on Header & TLS.
For a lot of this headers there are some good libraries for automating this which already have good default values, so most of them are plug and play.
|Ruby (and Rails)||Secure Headers; rack-secure_headers|
I did not talk about the XSS header, since it’s not supported at all in Firefox and Chromium will remove it in the near future, so I think it gives a false sense of security to devs not being vulnerable to XSS if they set the header.
Also I did not touch on the new Feature Policy header wich is currently being drafted. It’s very cool and will help a lot in the future. It allows websites to specify what features should be allowed to run, so e.g. if my site does not need the accelerometer just turn it off. That way no 3rd party code is able to access it. Very nice addition, but at the time of writing it’s still very alpha and basically not supported.
Where to store JWTs?
Most websites nowadays make use of JWTs for the authentication. A common question people have is: Where do I store my token? Cookies or LocalStorage? TLDR: LocalStorage.
The general knowledge is that LocalStorage is not vulnerable to CRFS and Cookies not to XSS. However as the reddit user Devstackr described here if you have a XSS vulnerability also your authentication via cookie is compromised, as the injected code can do requests on behalf of the authenticated user.
So while your token cannot be directly stolen from the victim, the attacker can still do everything, including changing the password for example. Additionally you don’t need to worry about CRFS at all, which is a huge bonus.
Large websites should additionally consider the following security features:
- Certificate Authority Authorization (CAA) DNS record which specifies what CA is allowed to sign certificates for the served domain.
- HTTP Public Key Pinning (HPKP) provides the option to validate the certificate via DNS record. If misconfigured this can break you entire site, so use carefully!