We’ve covered quite a few security related HTTP headers on the blog in recent weeks but the boss of them all has to be Content-Security-Policy (CSP). The boss, both because of the level of protection it provides but unfortunately also because of the difficulty of implementing it correctly on the first go. As with all of the flightPATH traffic management rules we’ve discussed recently, we use them ourselves to protect the edgeNEXUS website and its visitors.
We’ve done the hard work and felt the pain so you don’t have to but there’s no denying some thought and planning is required before implementation. It’s worth it though. The concept behind this header is pretty simple and equally powerful. In a nutshell, you use it to specify the allowed origins of content that may be loaded on your website. This provides strong protection against a number of code-injection attacks prevalent on the ever more dynamic internet today, such as Cross Site Scripting (XSS) and Clickjacking.
Before we get into that, let’s take a look at the elements of the header’s value and what a policy looks like. As you’ll see, it’s essentially a long list of content types (all ending with -src in this example) and the permitted sources for each type. Together these are known as policy directives.
default-src ‘self’ data:; script-src ‘self’ ‘unsafe-inline’; connect-src ‘self’; img-src ‘self’ data:; style-src ‘self’ ‘unsafe-inline’ data:; font-src ‘self’ data:; child-src ‘self’
There are further content types we can consider, but those shown above are the most important directives (in our opinion, based on risk and prevalence). Here’s the detail on what content each one relates to;
- default-src – Default values for most (but not all) content types if not specified later
- connect-src – Permitted sources for connections (such as WebSockets and EventSource used with, for example, chat applications)
- img-src – Permitted sources for images
- style-src – Permitted sources for CSS
- font-src – Permitted sources for fonts
- child-src – Permitted sources for frames and iframes
These content types and their values are separated from each other (delimited) with a semicolon. Each type can have one or more of the following values;
- ‘none’ – do not permit this content type
- ‘self’ – permit this content type if it originates directly from this site (but not subdomains)
- data: – permit inline data sources (typically used to provide fonts and images inline to improve performance)
- https: – only allow this content type over HTTPS
- ‘unsafe-eval’ – allow parsing of text using potentially dangerous methods that may result in code execution
- domain-name – permit this content type if it originates from the domain-name specified (in other words, a remote domain) – this can be used multiple times
- * – any domain
A simple space is used to delimit each value. The single quotes ‘ are required unless the value is a domain name. Note, browser extensions and plugins are exempt as these are considered secure because they are trusted by the user (who installed them after all).
It all looks a rather technical and complex, but if we tackle it one step at a time it’s pretty quick to come up with a policy. Let’s quickly work through two examples. First off, let’s try a simple internal web site served over HTTP. Here’s what we need;
- default-src ‘self’ data: – allow content from our own site only, including inline objects
- script-src ‘self’ ‘unsafe-inline’ – allow scripts from our own site, including inline ones
- connect-src ‘self’ – allow connections from/to our own site
- img-src ‘self’ data: – allow images from our own site, including inline ones
- style-src ‘self’ ‘unsafe-inline’ data: – allow CSS from our own site, including inline styles
- font-src ‘self’ data: – allow fonts from our own site, including inline ones
The full header value is reasonably short:
default-src ‘self’ data:; script-src ‘self’ ‘unsafe-inline’; connect-src ‘self’; img-src ‘self’ data:; style-src ‘self’ ‘unsafe-inline’ data:; font-src ‘self’ data:
Second, a SSL/TLS protected internet facing website that uses Google Analytics (GA), inline CSS, fonts and images, CSS from your site and images from various other websites. Let’s work through what we need piece by piece (it’s not very different);
- default-src ‘self’ data: https: – allow content from our own site only, including inline objects, only over HTTPS
- script-src ‘self’ ‘unsafe-inline’ https: – allow scripts from our own site, including inline ones, only over HTTPS
- img-src ‘self’ ‘unsafe-inline’ \* https: – allow images from any site, including inline ones from ours, only over HTTPS
- style-src ‘self’ ‘unsafe-inline’ https: – allow CSS from our own site only, including inline ones, only over HTTPS
- font-src ‘self’ ‘unsafe-inline’ https: – allow fonts from our own site only, including inline ones, only over HTTPS
Add it all together and we have this slightly longer policy:
default-src ‘self’ data: https:; script-src ‘self’ ‘unsafe-inline’ *.google-analytics.com https:; img-src ‘self’ ‘unsafe-inline’ * https:; style-src ‘self’ ‘unsafe-inline’ https:; font-src ‘self’ ‘unsafe-inline’ https:
Pretty easy I’d say. Google were not so CSP ‘friendly’ in the past (this header value used to be at least twice as long) but they have thankfully made great strides recently. You’ll note we’ve not had to add the *.google-analytics.com domain to the script-src as a content type value as their code is run as an inline script.
Ideally you’d create a policy per page on your website as the resources loaded will no doubt differ per page but this is rather onerous and it’s far simpler, yet still effective, to create a policy that covers the all possible sources. Most of the examples on our GitHub page take this approach but there is also one there should you want to provide greater protection to specific pages.
For testing and troubleshooting, I’d highly recommend using Google Chrome’s Developer Tools. Go to the site in question and then hit F12, click Network, ensure Disable Cache is ticked and then reload the page. Any errors will be highlighted clearly. At the bottom of this blog is a screen of what you might see if your policy is blocking unsafe-inline and you’re using Google Analytics; the error messages are very helpful.
Adjust your policy as necessary and rinse and repeat. Ideally you’d do this using a dedicated testing Virtual Service with the flightPATH rule you’re testing applied, but otherwise serving the same site, just through a different Virtual IP so you don’t affect real customers and clients while you’re testing.
As is always the case, the huge benefit of using a load balancer is that we only have to do this in one central place in order to protect all our servers (and sites). We don’t need to rely on developers or web server reconfigurations. On the edgeNEXUS load balancer we just import a jetPACK automatic configuration template and assign a flightPATH traffic rule to whichever Virtual Service(s) we wish to protect (after suitable modifications).
The rule only adds the header if it doesn’t exist so it’ll work even where our web servers already insert it or perhaps only insert it for specific pages. This rule should be part of your standard Virtual Service configuration – you’ve nothing to lose whatever the site although of course, testing is always recommended. You can download this jetPACK and many others on the edgeNEXUS Github site.
flightPATH is a dynamic, event-based rule engine developed by edgeNEXUS to intelligently manipulate and route load balanced IP, HTTP and HTTPS traffic. It is highly configurable and powerful, yet very easy to use.
These related links are well worth a read if you’d like to know more;
We’ve covered quite a few security related HTTP headers on the blog in recent weeks but the boss of them all has to be Content-Security-Policy (CSP).