Complying With the Latest Security Policies
Table of Contents
Modern websites and modern browsers support a wide range of security features to communicate specifically what is and is not allowed to be loaded, executed, or sent over the network. Being the person that I am, I’m going to comply with the latest guidelines and best practices as much as I can… and it’s a headache.
Content Security Policy
The biggest one has to be the Content Security Policy, which is giant list of “you can load X from Y”. Before showing my CSP, here’s a quick run-down:
You can specify controls for JavaScript, stylesheets, images, fonts, WebSocket / XHR connections, other non-image media, objects, embeds, & applets, prefetch / pre-rendered content, child content, <iframe>
targets, Worker sources (not the Cloudflare type), domains allowed to <iframe>
the site (see: X-Frame-Options
), allowed <form>
action
targets, upgrade HTTP requests for resources, block HTTP resources on HTTPS page, allowed document base URI, web manifests, and allowed plugins.
While most can take a list of domains, they can also take *
meaning “allow all”, self
and none
should be pretty self-explanatory, and.. well the rest I’ll get to in a second.
And yes, I know that disclosing security details is usually a bad thing, but it’s also sent over on every request, so it’s not exactly private knowledge.
Finally, this has been formatted for ease of reading:
default-src
'none'
script-src
'self'
'unsafe-eval'
'sha256-4ExdAblVQS3vP9+dOQPogQfTvVY/B4KGnQWH42NhRg0='
'sha256-CmjHf3aA+ooXJwVib2vComMSJWhXsR6tbZ+gj+s3zSU='
*.algolianet.com
https://ajax.cloudflare.com:443
https://static.cloudflareinsights.com:443
https://cdnjs.cloudflare.com:443
https://cdn.ko-fi.com:443
https://ko-fi.com:443
https://c.disquscdn.com:443
https://disqus.com:443
https://teknikaldomain-me.disqus.com:443
https://www.gstatic.com:443
style-src
'self'
'unsafe-inline'
https://fonts.googleapis.com:443
https://c.disquscdn.com:443
img-src
*
font-src
'self'
https://fonts.gstatic.com:443
connect-src
'self'
*.algolianet.com
*.algolia.net
https://links.services.disqus.com:443
media-src
*
object-src
'none'
child-src
'none'
frame-src
https://disqus.com:443
frame-ancestors
'none'
manifest-src
'self'
prefetch-src
https://c.disquscdn.com:443
https://disqus.com:443
upgrade-insecure-requests
block-all-mixed-content
default-src
Put simply: block everything unless explicitly allowed.
I could have gone with self
just in case, but… eh if every tool complains about self
, I’ll just use none
.
script-src
You’re likely not happy with this one.
The entire reason I need to allow the use of eval()
and those two hashes are because MathJax, the thing that allows me to write $\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}$
and get this: $\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}$.
The hashes mean that only a script with that exact cryptographic hash is allowed, instead of allowing inline JavaScript.
Luckily since there’s no unsafe-inline
(because hashes are used), you can’t inject scripts.
I also need to allow gstatic
for something cool: Google Charts.
And the wildcard on Algolia’s… well I’m not whitelisting their individual domains.
Algolia is the search engine that powers the search bar in the topnav, and trust me, it’s not tracking you.
All it has access to are the characters you type into that box so it knows what to give back in search results.
Fun fact: I allow Cloudflare insights (browser timings) to see if there’s something that I can do to improve performance, it’s completely aggregate data that cannot be linked to a single person, feel free to block it if you want, I just want to make sure there isn’t some obvious slowdown somewhere. Also note that this also blocks Google Analytics, which is indeed somewhere in the site’s theme files (that I have yet to remove, working on it), but that also means that yeah, there’s no GA tracking because your browser will just complain about a CSP violation until I excise it. Nice.
style-src
Also weird… again, MathJax puts style
attributes in everything, meaning I need inline styles.
Not as bad as inline scripts, but still a little insecure.
And yes, a stylesheet is loaded from the fonts domain. I don’t know.
img-src
Basically, just allow all images. Because of the last two directives, everything is forced to be HTTPS only, and I don’t know ahead of time what sites I might pull images from.
font-src
Fonts… self-explanatory.
connect-src
This allows WebSocket and XMLHttpRequest connections to be made. ‘self’ is likely not necessary, but a few things here use WS for communication, though they really shouldn’t in production. And the Disqus URL is for.. Disqus.
media-src
Non-image media, like videos, audio, whatnot. Heck with it, just include it all.
frame-src
What sites are we allowed to include in a <frame>
or <iframe>
?
Disqus needs it.
frame-ancestors
What sites can use us in a <frame>
or <iframe>
?
Nobody.
manifest-src
Where to pull a manifest file from. In this case, it’s site.webmanifest, which is this:
{
"name": "TeknikalDomain.me",
"short_name": "TeknikalDomain.me",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#1da82d",
"background_color": "#1da82d",
"display": "standalone"
}
All it really does is tell Android phones how to display everything when someone makes a shortcut to their launcher.
prefetch-src
Domains that you can prefetch or prerender content from before it’s required, saving a little bit of load time. Disqus.. again.. uses this.
Side note: For a long time, these kept failing because prefetch-src
doesn’t exist.
I checked and there’s a message above, stating that “The Content-Security-Policy directive prefetch-src
is implemented behind a flag which is currently disabled.”
The answer is chrome://flags/#enable-experimental-web-platform-features (for me), which needed to be enabled for the browser to recognize prefetch-src
.
Luckily content seemed to load fine once it was actually required, but the time-saving prefetch was blocked.
upgrade-insecure-requests
Any resource that has an HTTP URL will be automatically rewritten to HTTPS, no exceptions.
block-all-mixed-content
If a resource cannot be loaded over HTTPS, only HTTP, then refuse to load.
Cookies
I don’t actually use cookies on here.. at all.
Cloudflare does provide a _cfduid
cookie for.. whatever they’re doing.
if you’re doing cookies, make sure that all cookies are Secure
(meaning that they aren’t transmitted on a plain HTTP connection), and any backend session cookies are HttpOnly
(Scripts have no access).
Cross-Origin Resource Sharing (CORS)
CORS is a mechanism where one domain is permitted to load code or other resources from another domain.
Normally, the web browser will block cross-origin requests like this unless it sends an Access-Control-Allow-Origin
header, which specifies which domains are allowed to request a resource.
Since I’m not sharing anything, just not adding the header is enough to prevent it.
Expect-CT
Certificate Transparency (CT) is, simplified, a public list of issued TLS certificates that can be checked by anyone.
An Expect-CT
header means that the browser is to cross-reference the CT database, and refuse to accept the certificate if it does not exist.
For example: the header my website sends is:
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Now this one is part of Cloudflare, and I don’t have access to it.
what’s interesting is that it lacks an enforce
directive, meaning the browser will report a CT failure, but will still load the page.
max-age
is just the number, in seconds, that the browser should remember to check CT for this domain.
Feature-Policy
I.. don’t have one of these yet and it’s still very experimental. Like a CSP, a feature policy is a set of directive with an access list specifying what’s allowed to use them. Unlike a CSP, a FP is the set of policies for browser features, such as video autoplay, getting your device’s accelerometer data or battery status, use the microphone, there’s an entire list of these, which I won’t really get into right now, but suffice it to say that one acts like a CSP for browser features instead of resource types.
HTTP Public Key Pinning (HPKP)
Deprecated but I’ll still cover it.
HPKP was presented in the form of a Public-Key-Pins
header, which would specify the SHA-256 hash of the key that the website’s certificate was using.
You’re allowed to specify multiple, and had to specify at least two (one backup) for it to be honored.
Since the spec meant that you could pin any key in the chain, pinning your end certificate and then the intermediate certificate as a backup was what a lot of people would do.
The issue with HPKP was that, besides needing to constantly update headers, you could lock everyone out of your site if you pinned keys incorrectly, and HPKP has been deprecated in favor of Expect-CT
.
HTTP Strict Transport Security (HSTS)
HSTS is a mechanism where the browser is required to transparently redirect any HTTP requests to that domain to HTTPS.
The HSTS header, Strict-Transport-Security
, specifies up to three things: the max-age
, subdomain inclusion, and preloading.
max-age
is just the number of seconds where the browser needs to remember that this site is HSTS.
Once this amount of seconds elapses, plain HTTP requests are allowed (usually resulting in a redirect to HTTPS which resets HSTS).
For security, you want a long max-age
, I set 31536000
.
31,536,000 seconds is equivalent to 525,600 minutes, or 8,760 hours, or 365 days.
Yes, you need to not touch my website for an entire year before your browser is even allowed to think about making plain HTTP requests again.
I also send includeSubDomains
, meaning that any subdomain of teknikaldomain.me
(say, forum.teknikaldomain.me
for example) is also included in HSTS.
HSTS Preloading
Most browsers have a built-in (literally hard-coded source code) HSTS preload list, which is a list of domains that are known to use HSTS, and the browser should consider as always HSTS.
The site hstspreload.org can be used to query or submit sites to the list.
There’s a few requirements for preloading though:
- Your TLS certificate must be valid
- HTTP redirects to HTTPS on the same host
- All subdomains are HTTPS
- HSTS header must specify a minimum time of 1 year,
includeSubDomains
, and apreload
directive, which specifies that the site is requesting inclusion in the list.
You must continue to meet these requirements, or it’ll get removed.
Also changes to the list may only take place every couple months, since the list is actually hard-coded source, it only updates every major browser build.
Referrer Policy
When redirected from one link to another, browsers will send a Referer
sic
header, indicating the URL that it just came from.
For security reasons, this isn’t always good practice, which is why we have Referrer-Policy
, and it’s spelt correctly this time.
RP can take on only a few values, which I’ll go through one by one:
no-referrer
: Don’t sendReferer
.no-referrer-when-downgrade
(default): HTTPS -> HTTP connections omitReferer
, the rest keep it.origin
: Only send the origin (e.g., sendhttp://example.com/
forhttp://example.com/page.html
).origin-when-cross-origin
: Send just the origin when going to a different site, and the entire thing when staying on the same site.same-origin
: Only send header to the same domain, omit on cross-domain.strict-origin
: Only send over HTTPS.strict-origin-when-cross-origin
: Send everything when going to the same site, and only the origin to other sites. Nothing is sent over plain HTTP.unsafe-url
: Send everything, regardless.
I use strict-origin-when-cross-origin
, so as long as you’re going from one page of teknikaldomain.me
to another page of teknikaldomain.me
, it’ll know what you did.
But if you clicked off a link to a different site, all it will know is that you came from teknikaldomain.me
but not exactly where.
Additionally, if you’re visiting another site over plain HTTP, no header is to be sent at all.
Server Header
The Server
header isn’t a real security header, but it can be a security risk.
Usually the web server that’s… serving the content will fill this in, say with like Server: nginx
or something.
Knowing the backend server in use is actually a security leak, since now an attacker knows what you might be vulnerable to.
Cloudflare automatically masks this with Server: cloudflare
, so it’s pretty useless for determining anything.
Any value here really works as long as it does not give away the actual program that’s doing the handling, like Apache, Nginx, Caddy, or IIS.
You can still use Server
to indicate, say, which datacenter a request was routed to, got debugging.
Just don’t stick program names in there.
Subresource Integrity (SRI)
The one that… I literally cannot do.
For resources, like JavaScript, that are loaded from an external domain, there’s no real guarantee that you’re loading the correct thing.
This is why, for example, the <script>
tag has an integrity
attribute, which specifies the cryptographic hash of the resource.
As such, if the hashes do not match, the browser will refuse to load what it got in response.
Essentially, you’re saying that this resource and only this resource are allowed, regardless of what the name says.
The issue is that for SRI to work, resources need the proper CORS header, and of the two external resources I load, neither do.
Given that they’re both resources from CDNs, the fact that they don’t is confusing in its own right, actually.
Furthermore why don’t browsers reject it then?!
Regardless, what that means is that I could add in an integrity
attribute to the scripts, but the browser would refuse it because the resource domain isn’t authorizing CORS requests.
X-Content-Type-Options
Servers usually send a Content-Type
header to the browser, telling it the MIME type of what it just requested.
For example, asking for test.png
will have Content-Type: image/png
on it.
XCTO only has one value, nosniff
, which means that browsers should, get this, respect the Content-Type
header and not try to deduce the content type themselves.
The need arose when ’type sniffing’ (automatic file type detection) was turning non-executable types into executable types back in the early days of the web.
Either way, why do we need to tell browsers to literally just not tamper with a value they’re already given‽
X-Frame-Options
Superseded by CSP’s frame-ancestors
directive.
Essentially, when a browser loads a page through an <embed>
, <iframe>
, or similar mechanism, it checks the XFO header, if any.
I set it to DENY
, meaning no attempt to frame the page will succeed, and the browser should refuse to load like that.
Alternatively, you can use SAMEORIGIN
meaning that you can only be framed by yourself.
X-XSS-Protection
Most modern browsers have a cross-site scripting (XSS) auditor that will attempt to block what it thinks is XSS, for example, someone leaving a comment that is just some <script>
.
The header can have a base value of 0
or 1
.
Zero means to not protect (why?), and one means to ask the browser to enable XSS protection.
If any is detected, it will be deleted.
If mode=block
is added, then instead of deleting it, the browser will just fail the page load, and complain that due to XSS it’s not going to.
Like most things, it’s a little obsolete now, especially with CSPs in place.