Useful HTTP Security Headers

18 Feb 2020

In a previous blog post, I wrote about a whole bunch of HTTP headers which compromise your security and how to disable them all on many different types of servers. This time, I discuss “good” headers and give instructions for enabling. I also delve into “possibly good” headers including when you might need them and “supposedly good, but not really” headers explaining why they are a waste of time.

Useful Security Headers

All security headers presented here are good ideas to add to your website.

Content-Security-Policy (CSP)

The Content-Security-Policy (CSP) header is a powerful mechanism to protect your site. It can define from where JavaScript can be loaded, if the pages may be framed, and force encrypted HTTPS transport for all resources. There are three “levels” of CSP and each successive level adds more security features, sometimes deprecating and replacing older features. Generally, Chrome supports all non-experimental features, Edge / Firefox / Safari support nearly all features, and Internet Explorer is a dumpster fire rolling down a boulevard crowded with innocent kittens and puppies. The full compatibility list can be found on the browser compatibility list.

The basic format directive, parameter(s), semicolon, repeat; like so:

Content-Security-Policy: {directive} {parameter} {parameter}  {…} ; {directive} {parameter} {…} ; 
Content-Security-Policy: {directive} {parameter} ;

The parameters are usually URLs containing wildcards, like https://*.example.com, or special values like 'none' (meaning disable) or 'self' (meaning from the same page/webserver). More nuances can be found at Mozilla.

Directives can be indicated in one CSP header line or split across multiple CSP headers. When multiple headers are present, the security specified is cumulatively restrictive; ergo, later headers do not override previous ones. For example, if the first header specifies something is supposed to be set to “maximum security” and a later header says “medium security”, then the maximum security setting remains enabled.

This makes it easier to specify headers in different parts of the application stack; app server, web server, reverse proxy, and WAF may add new headers of last resort as necessary.

However, it is not recommended to split the values for a directive across multiple headers. If an earlier headers says “allow directive-X to be used only on example.com” and a later header says “allow directive-X to be used on another-site.com and example.com”, then the “maximum security” of the first header limiting the access may apply.

Generally, you should disable features even if your site does not use them. For example, add a CSP directive to disallow execution of Flash objects to prevent exploits even if an attacker finds a vector to insert a SWF file.

Here are some of the recommended Content-Security-Policy directives.

CSP: frame-ancestors

To prevent Clickjack attacks, the frame-ancestors directive can be used to indicate if the content is allowed be placed inside of an <iframe>, <embed>, or similar construct. In most instances, sites want to disallow framing, so the proper directive is:

Content-Security-Policy: frame-ancestors 'none';

However, if you want to allow framing from the same host, use:

Content-Security-Policy: frame-ancestors 'self';

Or, use something more complex to enable framing from multiple sources:

Content-Security-Policy: frame-ancestors 'self' *.mydomain.com https://anothersite.trusteddomain.com;

The frame-ancestors directive obsoletes the X-Frame-Options header, except in IE.

CSP: upgrade-insecure-requests

This CSP directive automatically converts many http:// URLs into https:// URLs. Basically, you can use this to quickly update your site to use HTTPS for most everything. The upgrade-insecure-requests directive will upgrade all URLs on the same site and some URLs for third-party sites. Requests to picture.jpg will always be done over HTTPS on any site, but requests for navigational items on other websites, like http://not-example.com/home/, will remain HTTP. To enable:

Content-Security-Policy: upgrade-insecure-requests;

The “navigational items” are generally defined as out-bound links elsewhere. So don’t rely on this header for absolute HTTP → HTTPS protection of third party links.

If your website uses Strict-Transport-Security (HSTS), should you also use upgrade-insecure-requests? There is a lot of overlap between the two, but HSTS only works on your site and can protect the initial in-bound request and the CSP: upgrade-insecure-requests gives some limited protection outside of your website. So, basically, use both. Then, encourage remote sites to also implement HSTS to give you full HTTPS-everywhere protection.

CSP: default-src

The previous directive automatically upgrades many items, but not all. We cannot force the update for some calls to third-party sites, but we can stop insecure outbound connections from even happen with the default-src: https: directive. Now, these external links will not work, thus protecting your site. This directive forces all traffic to be transmitted via HTTPS by telling the browser to not perform HTTP requests. A reasonable default for most sites is:

Content-Security-Policy: default-src: https:;

Minimally, you want to specify the https: parameter to force some additional transport-layer security. But realistically, you want to specify all servers from which pages might be loaded, such as https://.yourdomain.com* and third party sites, for example:

Content-Security-Policy: default-src: https: https://*.yoursite.com https://*.bootstrapcdn.com;

CSP: script-src

If script-src is not specified, it falls back to the value from default-src. If you used the default-src: https: directive above, browsers will require everything to be delivered via HTTPS. This includes JavaScript code – it must be in a file sent to the browser over HTTPS. But face it, your site has inline JavaScript on pages all over the place and nothing will run since it is not a separate file. Thus, for many websites, you must specify the parameter of 'unsafe-inline' too. This extra parameter allows inline JavaScript embedded on the page to execute and forces all other JavaScript to load from HTTPS.

Content-Security-Policy: script-src: https: 'unsafe-inline';

This setting provides you with good first start at locking down your site. If you use eval() in your JavaScript code, you also need to add the 'unsafe-eval' parameter to the directive line above. However, you really should lock your script sources down even further by specifying a full setting, such as:

Content-Security-Policy: script-src: https: 'unsafe-inline' https://*.yoursite.com https://ajax.googleapis.com https://*.example.com;

If your site doesn’t have inline JavaScript, you are probably safe relying on the default-src settings.

CSP: object-src

Flash, Silverlight, and other plug-ins are objects in CSP. A simple web query about Flash security vulnerabilities over the years will tell you that allowing these plug-ins is a bad idea. As such, disable objects everywhere with:

Content-Security-Policy:  object-src 'none';

CSP: media-src

If your website does not use <audio>, <video>, or their related tags, then disable them. There is no point in allowing a joking attacker to add a Rick Roll if you are compromised.

Content-Security-Policy:  media-src 'none';

CSP: child-src

The child-src directive indicates from where your webpage can load frames or other embedded content. Generally, this can be thought of as the opposite direction of frame-ancestors. If your page does not use frames, then set this to none.

Content-Security-Policy:  child-src 'none';

Otherwise, set a reasonable set of sources for your frames:

Content-Security-Policy:  child-src 'self' *.mydomain.com;

A similar directive, frame-src, can also be considered, but only for frames. If not specified, the value of child-src is used.

CSP debugging & attack detection

Because CSP configuration can be complicated and may differ between browsers, it possible to ask a browser if your policy works as you intended using the report-uri and report-to directives. If CSP violation occurs, such as due to some incorrect setting on your end or because the browser prevented an attacker from deploying a clickjack attack, the browser will sent the report to the specified URL.

Content-Security-Policy: (list-of-CSP-settings); report-uri /debug-csp/

Note that report-uri has been deprecated in favor of report-to. If both are provided, then the browser ignores report-uri and only uses report-to. Since Chrome only recently implemented report-to and because no other Blink-derived or Chrome-derived engine supports it, we recommend holding off implementation of report-to until there is wider coverage.

You can also use the Content-Security-Policy-Report-Only header to test if something is wrong without the browser executing the CSP directive. Unlike the report-uri directive above where only violations generate a report, using the Content-Security-Policy-Report-Only header will generate and send a report for every request. Due to the amount of data you could receive, you may wish to limit the use of the header to certain testing periods.

Content-Security-Policy-Report-Only: default-src https:; report-uri /debug-csp/

The CSP debug reports are sent in the JSON format described here. Remember, when you collect these reports that an attacker could pollute your data stream or even try a nasty Deserialization Attack, so protect yourself. Generally, no cookies will be sent with the report, so expect unauthenticated data.

There are various free, commercial, and SaaS/Cloud CSP reporting engines available, this blog provides some guidance.

Strict-Transport-Security

HTTP Strict-Transport-Security (HSTS) is a mechanism which tells the browser to send all traffic using encrypted HTTPS and never to use cleartext HTTP, even when accidentally specified. When the browser sees the header, it knows that all future requests must be HTTP – even after logging out and clearing the cache. The browser will use this Strict-Transport until the expiration.

To use, you must send the HSTS header on the first response sent to a browser, such as to www.example.com or www.example.com/login. It is safe to send the header on all requests, but wasteful of bandwidth. If the HSTS header is sent over cleartext HTTP, the header will be ignored. Additionally, for the purposes of HSTS, example.com and www.example.com are considered two different websites. Here is an example which forces HTTPS for the next six months:

Strict-Transport-Security: max-age=15768000;

It is also possible to force all subdomains of your domain to also be included by specifying the includeSubDomains parameter. Be careful when specifying this as it also forces sites like debug.example.com and test.example.com to be served over HTTPS – port 80 will not be used. Thus these possibly temporary or testing sites must also have valid TLS settings and certificates configured.

Another useful parameter is preload which specifies that you want your site and all of its subdomains to always be sent over HTTPS, even if the user has never gone to your website. Essentially, you want to “preload” the browser with your sites – i.e., compile it into Firefox or Chrome. The preload directive has some additional requirements which must be followed for your site to be added to the list; please see instructions at the HSTS clearinghouse. Specifying preload on its own without following the clearing house direction essentially has no effect.

Thus, in a perfect world, you might use:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

When trying to get the HSTS header to work, I suggest you set max-age to a few minutes so that end users don’t permanently lose connectivity to cleartext HTTP portions of your site or subdomains by accident. Test thoroughly before increasing the expiration and submitting to the preload list.

If configuring HSTS proves difficult, using Content-Security-Policy: default-src: https: is an imperfect and temporary work around.

X-Content-Type-Options

Sometimes, browsers try to guess what type of data is provided instead of relying on the Content-Type header. This can lead to attacks and “forced file downloads”. You can tell the browser to stop sniffing the content to prevent these attacks with:

X-Content-Type-Options: nosniff

It is safe to always send this header, pretty much with every response. More information can be found at Mozilla.

Semi-Useful Security Headers

These headers are sometimes recommended, but they usually are not needed. Think about if you need them before implementing.

CSP: block-all-mixed-content

Say it ain’t so – telling me to not use a CSP directive? It’s because you don’t need it. The **block-all-mixed-content* directive won’t let HTTP elements appear on a HTTPS page (aka, mixed content). Since you’re already using Content-Security-Policy: default-src: https:; and/or Content-Security-Policy: upgrade-insecure-requests;, you are already covered.

CSP: most *-src directives

Content-Security-Policy offers a plethora of source directives to lock down your site. You can specify that images can load from one place or scripts from another. Here is the big list of all source directives:

  • child-src ✅
    • frame-src
    • worker-src 🧪
  • connect-src
  • default-src ✅
  • font-src
  • img-src
  • manifest-src
  • media-src ✅
  • object-src ✅
  • prefetch-src 🧪
  • style-src
    • style-src-attr 🧪
    • style-src-elem 🧪
  • script-src ✅
    • script-src-attr 🧪
    • script-src-elem 🧪
    • worker-src 🧪

Note: items marked with a check (✅) are recommended to be set; with a test tube (🧪) are experimental.

So, why are all of these *-src in the “semi-useful” section and not the “OMG, use this now” section? Mainly because you already have the defaults set. Most websites are fairly self-contained or load from a CDN. As such, you’ve already configured your defaults and probably don’t need to fine-tune for scripts versus style sheet sources. That’s not to say using these other directives isn’t useful (they are), but most people probably don’t need them.

In all cases, if nothing is is specified for the directives above, default-src will be used. For the nested directives, the lower level directive defaults to the higher level one and if that is not given, default-src is used. For example, style-src-elem defaults to style-src defaults to default-src. An exception is worker-src which defaults to child-src, then script-src, then default-src.

If you run a large website or do commerce, by all means, implement some of these. But for your own tiny blog – probably not needed. If you start embedding google analytics or WordPress linkages, you’ll have to do more complicated script-src lines.

Referrer-Policy

When browsers load another page, api, link, image, etc, they send a Referer header to the server that shows exactly where the browser came from (yes, it is spelled incorrectly in the specification). The Referer header is sent for nearly all connections and contains the entire URI; for example, after authenticating to a site, a third party analytics engine may be loaded from the page http://example.com/login/username=AzureDiamond&password=hunter2. As you can see, sending this URL as a reference could be problem and the Referrer-Policy header prevents this from being sent.

There are a few configuration options, but probably the most useful ones are:

Referrer-Policy: no-referrer
Referrer-Policy: no-referrer-when-downgrade      # default
Referrer-Policy: same-origin
Referrer-Policy: strict-origin-when-cross-origin
  • no-referrer – never send the Referer header.
  • no-referrer-when-downgrade – send full URI to everyone and nothing on unencrypted http connections. This is the default
  • same-origin – send full URI to myself, nothing to other sites
  • strict-origin-when-cross-origin – send full URI to myself, only the webserver name to others, and nothing on unencrypted http connections.

Depending on the type of site you run, select the appropriate header above (or stick with the default). For small blogs or e-commerce sites, the linkages are desired. If your tiny website drives user clicks to a larger company, it is probably good for you if they know who you are. However, for websites containing sensitive data, it is wise to lock down the referrer to prevent possible data leakage. Your insurance provider allowing a referrer of https://myaccount.medical.example.com/cancer to a search engine to whom you’re probably already authenticated gives away a great deal of personal information.

You may recall that <a> anchor links in HTML have an optional parameter rel="noreferrer” which indicates to not send the referrer for that link, but that only works for links. Using Referrer-Policy: no-referrer header can automatically do this for all links – along with images, JavaScript calls, etc.

Unfortunately, the Referrer-Policy header is not supported on IE or Edge nor on any iPhone browser including Chrome for iOS and Safari for iOS.

As this header appears to be gaining traction, you may wish to consider adding it to your roadmap.

Expect-CT

The Expect-CT header informs the browser to do an additional level of validation on the TLS certificate chain when used on your website during this HTTPS connection. This header tells the browser to reach out to a public Certificate Transparency log and validate the certificate is not being using in a questionable way. While there is nothing bad in this security check – it probably already is performed without you adding this header. As long as you’re using a major-name certificate authority to sign your certificate, even the free Let’s Encrypt service, the check is automatically done.

However, if you want that you do need it, use something along these lines:

Expect-CT: max-age=86400, enforce, report-uri="https://www.example.com/CT-report"

Generally though, this is not needed since it is already done most of the time.

Feature-Policy

This Feature-Policy header tells the browser that the page wants to use certain browser feature, such as the end user’s camera, but that other parts of the page must be disallowed from accessing those features. To build on the camera example, the main page was allowed to utilize the camera by the end user, but the third-party JavaScript code loaded from a different site did not have the policy set to allow, so access to the camera by this other JavaScript is denied. Thus, this header is a mechanism to request access to features from the user and to deny this granted access to sub-components of the website.

The Feature-Policy header’s syntax allows resources to be specified in a manner nearly identical to Content-Security-Policy. So, your website at https://example.com loads a bunch of analytics collection scripts from a third party site and also contains an <iframe> that loads an audio-video chat service from another technical support site. Your page might send the header:

Feature-Policy: camera https://another-support-site.com; microphone https://another-support-site.com

This header would not allow your example.com site nor the analytics site any access to camera and microphone, but allow another-support-site.com’s to access to the AV. This demonstration is described in Example 3 in the W3C Feature-Policy Draft document.

The draft standard of Feature-Policy is very new and subject to modification with browser support varying. All mobile-only features, like accelerometer and gyroscope, only work on Android and only in some Android browsers. Safari on MacOS and iOS does not even support the header; they only support this feature via an allow parameter of an <iframe>.

Though it does not offer as fine-grain of control, the CSP: sandbox header allows you to lock things down. At this point, it is best to wait until the Feature-Policy header fleshes itself out and to utilize legacy technology to access the camera and microphone.

X-Frame-Options

The only reason to use X-Frame-Options (XFO) today is to provide support for users of Internet Explorer as this header has been made obsolete by CSP’s frame-ancestors directive described above. The XFO header tells the browser if the website may be encapsulated or subsumed inside of a frame; basically the sole purpose of XFO is to prevent Clickjack attacks. There are two common forms: forbid all framing and allow framing from the same website – the headers, respectively, are:

X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN

On non-IE browsers, the X-Frame-Options header is ignored if you also include a Content-Security-Policy: frame-ancestors header. The CSP equivalents to the above are:

Content-Security-Policy: frame-ancestors 'none';         # aka DENY
Content-Security-Policy: frame-ancestors 'self';         # aka SAMEORIGIN

If you care to learn more about this nearly-obsolete header, more information can be found at Mozilla. As there is still significant real-world IE usage, you should include one of the two forms shown above.

If you have website which does not function at all on IE and requires Edge, Firefox, or something more modern to run at all, then you do not need to include a XFO header.

Obsolete & Unneeded Security Headers

These headers are sometimes recommended. Don’t waste your time.

HTTP Public Key Pinning (HPKP)

HTTP Public Key Pinning (HPKP) is the idea that a server tells the browser only connection if the certificate’s public key fingerprint matches one of the provided fingerprints. The browser will refuse all other connections until the expiration of the pinning. While this might sound good, to only recognize a certain public key, in practice, using the Public-Key-Pins header can lock a browser from the ability to ever connect to your website until the expiration, which generally was up to one year. So, if you configure it wrong, you effectively go offline. Joyous.

HPKP configuration is difficult and error prone. In fact, browsers have removed support for it due to the constant lockouts. This header is officially obsolete and has been removed from various web standards and has been replaced by a combination of Certificate Transparency and Expect-CT.

X-XSS-Protection

The magical X-XSS-Protection header supposedly stops Cross Site Scripting attacks. It doesn’t work. Not only does it not catch all “legitimate” XSS attacks, it is worse. The header can be used to disable legitimate JavaScript, adds cross-site referrer leakage, and also introduces an additional XSS attack surface. Firefox never implemented it, Edge ripped it out, and the Chrome family will remove support soon. So the best solution: don’t waste bandwidth on it.

CSP: referrer

This CSP directive is obsolete and is no longer supported by any major browser. If someone recommends you implement Content-Security-Policy: referrer 'none';, do something better and use Referrer-Policy instead.

Pragma: no-cache

The Pragma header was designed to allow non-standard headers or commands to be communicated. Instead of just sending a header, the client or server would send Pragma: command. It was never widely adopted once designers realized they could simply just send another header. In practice, the only usage of Pragma was to disable caching before the standardization of the Cache-Control header. However, security practitioners still recommend sending it, “just in case”, for older browsers.

Just stop it. It is dead. As a Parrot.

References

About The Author

This blog post was written by Jay Ball and published on Jay’s blog at veggiespam.com. Jay is an #infosec professional who does penetration testing, security threat modeling, security compliance, security architecture, and security whiskey in many of the skyscrapers throughout Manhattan and Jersey City. He participates in local infosec organizations such as OWASP, 2600, HackNYC, and more. You can reach Jay via DM on Twitter @veggiespam or via email.

Original post by veggiespam - check out veggiespam