Experimenting with new HTTP Security Headers
Recently, there was call at the day job to install new HTTP security headers. These headers mainly serve to do things like prevent code served by your system from *including* code that does things the user wants to do.
Effectively, it is a standard, static Apache download page (with FancyIndexing) to grab open source software from. As with many things, I started out myself by testing things on my own Apache server over on Gushi.org, before applying anything to the day job.
Explaining the Headers
The page I used to figure out which headers we needed to add was SecurityHeaders.com, which scans your site and gives you yay-or-nay on several headers of note (in the order they list them):
I’ll go through these in order, and at the end, I’ll show how I nailed these into my Apache httpd webserver, as well as how we enabled things on our Fastly CDN.
Content-Security-Policy or Content-Security-Policy-Report-Only
This header basically is a header that says, in effect, things under this domain can only be loaded from these places in these ways.
If you are using the report-only variant of this header, it means that rather than blocking content, the browser should simply report to a URI (such as what I’m using with report-uri.com, below) and say, in browser-ese “Here’s some stuff that I would have blocked according to your existing policy”.
Content-Security-Policy: "default-src 'self'"
Not everyone’s policy will be this simple. For my own domain, I signed up for an account at https://report-uri.com. Not only do they have clickable tools that will help you generate a policy (they can get quite advanced), but you can set a header so that browers will report violations of that policy back to them, and you can adjust your policy as you go along. Currently, they’re suggesting the policy I should have in place is:
Content-Security-Policy: "child-src 'self'; default-src 'self' 'unsafe-eval' 'unsafe-inline' data:; frame-ancestors 'self'; frame-src 'self'; img-src 'self' www.gstatic.com www.w3.org; script-src-attr 'unsafe-inline'; script-src-elem 'unsafe-inline'; script-src 'unsafe-eval' 'unsafe-inline'; style-src-attr 'unsafe-inline'; style-src-elem 'unsafe-inline'; style-src 'unsafe-eval' 'unsafe-inline'"
That’s a mouthful! And you’ll see there’s a number of “unsafe” words in there. According to CSP, your pages shouldn’t simply have embedded
unsafe-eval is basically your way of saying “I know I’m doing a bad thing, but at least I’m declaring it”.
I found a lot of useful reading in this Google Article: https://csp.withgoogle.com/docs/strict-csp.html
This google web fundamentals article was also useful: https://developers.google.com/web/fundamentals/security/csp
So, X-Frame-Options is a tool to prevent “clickjacking”. Unlike some of these headers, there’s only two valid values you can set, that either mean “don’t let this page be framed at all” or “only let it be framed on the same base URI”. These are the two valid options; they are mutually exclusive:
There’s more info at this Mozilla Developer page: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
We set the
SAMEORIGIN policy, because the code we were using defaulted to it, even though we do not use Frames.
Arguably the most simple header here, this header stops a browser from trying to MIME-sniff the content type and forces it to stick with the declared content-type sent by the webserver. The only valid value for this header is:
So that’s what we set.
Since the beginning of the web, sites have been able to tell what site you clicked on to get to a given page. In a privacy-aware world, this could be bad. Users don’t know it’s happening. (Users often don’t know a lot of things are happening, separate rant.)
Sometimes, the mechanics of ones own website require that referrer to be sent. (i.e. you jumped from an article to the general comment section, that’s important). The referrer header is also critical in preventing off-site “hotlinking”.
And arguably, there is some value not only in knowing which sites link to you, but also how many users are following which link, but the argument seems to be that this should be up to the referring sites.
Originally, I had set:
But the securityheaders.com website gave a warning for this (cryptically only saying it’s “not recommended”. I’ve changed it to:
The spec is at https://www.w3.org/TR/referrer-policy/
However, I found the best document, with actual examples of what each policy did, where the origin was the same and differed only via HTTP/HTTPS was at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
There is not, annoyingly, a permissions policy knob to stop sites from asking to show notifications in your notification center.
Unlike every cooking site on the internet, I’ll give you the recipe I used up front. It’s not a one-size-fits-all thing. Don’t blindly paste this stuff.
Here’s a fully “null” Permissions-Policy (generated by https://www.permissionspolicy.com). There are nice mouseovers on that site that explain what each thing means:
Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()
HTTP Strict Transport Security is a header that basically lets you say “Don’t ever load this page over plain vanilla HTTP again”. It prevents what’s called a “downgrade” attack. Additionally, it lets you say, in effect “and remember this session across browser restarts, for up to a long period of time (on the order of weeks).
We’ve had this header in place for a long time. Long before we were evaluating things using the securityheader.com site, it was required to get a good score using the SSLLabs.com site (which checks your HTTPS ciphers, certs, and the like).
Note well that this is one of the easiest headers to shoot your foot with. If you set
includesubdomains and you aren’t very ready to serve everything over https, you can seriously break the user experience in a bad way due to the caching of this header that happens.
In our case, the header, for completeness, is:
Strict-Transport-Security: max-age=31536000; includeSubDomains
That’s 365 days, in seconds. This was automatically set up by our CDN, Fastly. (As in, there is a single knob to turn this on, which we set when we enabled HTTPS.)
Unlike the rest of what I’ll describe later, there was no need to set any custom Varnish configs.
Putting it all in to place
So, as mentioned, my personal system where I workshopped this is running the Apache HTTPd. I do shared hosting, and Wordpress and the like come with a lot of knobs that expect .htaccess files to have an Apache syntax. So while NGINX or Lighttpd are great products, I use the tried and true classic.
mod_headers module is responsible for setting HTTP headers for me, and in my case, that config snippet (inside my
<VirtualHost> block), look like:
Header always set X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy no-referrer
Header always set Feature-Policy "camera 'none'"
Header always set Permissions-Policy "autoplay=(), microphone=()"Header always set Content-Security-Policy "default-src 'self' 'unsafe-inline' *.youtube.com code.jquery.com; font-src 'self' fonts.googleapis.com fonts.gstatic.com;"Header always set Content-Security-Policy-Report-Only "default-src 'none'; form-action 'none'; frame-ancestors 'none'; report-uri https://gushi.report-uri.com/r/d/csp/wizard"
The break on the last two is just to display them. They’re a single line in the config, but Medium’s code formatting is not the best.
In the last link, you can see that I am using the report-only variant of the content-security policy, to try and fine-tune my policy before anything gets blocked out of hand. Even the little “W3C Valid HTML” box I have on some of my pages, needs an exception for this. So do your little Norton site security seals, or magic Share widgets, or weather embeds, or anything else you might have that loads anything from off-site.
Note that we send
always set because we’d like these set even in the event of an error code.
Making this work with Fastly
It turns out there’s a great article on developer.fastly.com that tells you how to take a VCL (Varnish Control Language) snippet and apply it to your Fastly site, at a specific point in the delivery process.
Here’s the article: https://developer.fastly.com/solutions/examples/enable-modern-web-security-headers-to-all-responses, and while we of course had to tune the exact headers and their values to our needs, this made the process pretty effortless. Fastly validates any VCL you put in, so if you forget a closing quote or something, you can’t just break everything.
Static sites still are a thing, of course, and they can still look great with the right CSS, but the people who make the browsers and define the web protocols as they evolve are trying to make sure that a security-conscious server operator has some control over what happens after the page is served to the browser — that they can effectively give the browser the equivalent of “road signs” about what to expect, and how safely to proceed.
Securityheaders.com makes mention of several up-and-coming headers (at time of writing, they mention Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy, and Cross-Origin-Resource-Policy), and with SSL itself having evolved several times over the past few years, this is going to continue to be a changing landscape.
Just as with enabling DMARC, there’s a requirement on the server operator to continue to monitor for feedback and reporting of failures, and that means an ecosystem of monitoring services and record generators will spring up over time. (My use of one I found for free is not any kind of official endorsement on the part of my employer.)