Content security policy, what it is, what it does, and how to implement it

Cross-site scripting or XSS is ranked third on the OWASP top ten list. In an ideal world I wouldn't have to explain what an XSS attack is, but because it's ranked so high, I feel like I need to explain it briefly (because obviously a lot of sites are open to this sort of attack).

What is cross-site scripting?

Cross-site scripting is a pretty straight forward attack vector. If user input isn't properly sanitized when it's outputted to the browser, the site might be susceptible for an XSS attack. One of the most common ways to detect if a site has an open hole is simply to try and input something like;

// Paste this into an input-field and submit the form 
<script type="text/javascript">alert('XSS');</script>

Upon posting that string to the server, if you're greeted with an alert popup you've successfully injected a piece of javascript into the page, ergo, cross-site scripting.

There's generally three types of XSS attacks. The first is a persistent vulnerability (known as a stored injection), where the server saves the input and renders it out to other users who visit that url. For example a forum where the posts aren't properly sanitized, if you write a post containing the payload, each and everyone of the users who will read that post are going to evaluate that piece of javascript.

The second type is a non-persistent XSS vulnerability (known as a reflective injection). This is something you usually find in search forms, when you search for something the page will render out the result, but will also write out the exact search-term that you used. If the search-term isn't sanitized you'll be able to exploit it. The only problem with a non-persistent attack is that you'll have to trick your victim into clicking on a custom url that contains the payload.

// Example of a malicious url

The third, and probably one of the hardest to exploit is known as DOM injection. Where a user can inject a payload during runtime and get the script to be evaluated. For example if you have an input field, where the user can enter something, and when the user clicks on the button the inputfield data is entered into a DOM element. If the user inputs a script-tag, and the application doesn't sanitize the data, the script tag will be evaluated by the browser once it's written out in the DOM element.

If you want to know more about XSS, and maybe a get a proper explanation with more examples, I'd recommend reading up about it on OWASP's site.

So now that we've quickly gone through what an XSS attack is, I'll assume that you're thinking "That's really not that bad, who cares if somebody gets a popup or not". XSS attacks aren't generally used to annoy the end-user, but rather to exploit the end-user's credentials. Suppose I was a user with administrative credentials on a forum, and you crafted a payload that reads all the cookies from my browser and then sends those cookies to your own server. If I, as the admin, where to open that thread with your custom script all my session cookies would be sent to your server. Once you have those cookies you could potentially hi-jack my session to the server and gain administrative rights to the forum.

How to protect against XSS attacks

In order to protect your website, and your users, you'll have to sanitize all the data that is rendered out to the browser. If you're using PHP, strip_tags will probably be the easiest way to handle the data. The biggest problem with strip_tags is that it removes every tag that it finds. A generally nicer solution is to pass everything through htmlentities, which translates all special characters to their html entity equivalent (i.e. the character < becomes <). This means that everything the user enters will come out, but because it's been converted to the html characters the browser won't try to (and frankly, can't) evaluate it.

// Sanitizing user generated content.
echo htmlentities('<script>alert("xss");</script>');
// This will output;
// &amp;lt;script&amp;gt;alert(&amp;quot;xss&amp;quot;);&amp;lt;/script&amp;gt;

But because a code base can become humongous, and you might need rely on third party libraries or frameworks, making sure that everything is cleaned properly is going to be pretty cumbersome. Sifting through hundreds of thousands lines of codes is pretty much out of the question. So what can you do? The answer is Content-Security-Policy, a response header that dictates from where resources are allowed to be loaded, and if the browser is allowed to parse inline styles or run inline Javascript (which an XSS attack essentially is). Now keep in mind, before I go any further, that content security policy is not a get-out-of-jail free card, you'll still need to sanitize everything, you should treat this header as the last line of defence, an insurance policy.

Content security policy

CSP is pretty strict out of the box, and very few sites will work as they are intended to if you add it with only the base parameters. One of the key features with CSP is that you can tell the browser to invalidate all inline javascript, this has the side effect of not allowing you to add any of your own JS in the DOM tree (because the browser has no way of differentiate between your scripts and maliciously injected scripts). You can turn off this feature, but by doing so the whole point of CSP will be lost, and no extra protection will be given. In other words, if your starting up a new project, great, it'll be a lot easier to avoid adding script tags to the html markup. If you're going to implement CSP on an existing site the first thing you'll have to do is cleaning, this will probably be a lot of working and a lot of refactoring on the front end code. And this is a good thing, separating the Javascript and CSS from the markup will make life easier for you in the long run.

You can also add different domains that are allowed to load content to your page, by default, no content is allowed to be loaded at all. You'll actually have to specify your own domain as a trusted source, luckily, the engineers who came up with this also had a solution, the default-src.

// Set the default source to the own domain.
Content-Security-Policy: default-src 'self';

If you add that header you'll be able to loading everything from your own domain (images, stylesheets, scripts, etc.). We can also fine tune the policy, you might want to allow some things from other domains, while only allowing scripts from your own domain. The following really depends on what kind of page you're working on. Different types of services need different kinds of security settings, a blog for instance, might want to be able to load youtube and vimeo videos, while a banking page wouldn't need any of that.


Because the combinations and needs are endless, I'll use my own blog's configuration as an example, this is the Content-security-policy that I deliver with every page view:

	default-src 'self';
	// Allow images from any domain
	img-src *;
	// Only allow scripts from my own domain
	// and from google analytics
	// Allow frames from any domain (embedding)
	frame-src *;
	// Allow object source from any domain (embedding)
	object-src *;
	// Allow styles from my own domain, allow inline styles
	// and allow styles from google fonts
		'self' 'unsafe-inline';
	// Allow connect-src from my own domain and from youtube.
	// Allow fonts from my own domain and from google.

One thing that might catch your eye is the style-src, the 'unsafe-inline', this allows the browser to parse the style-attribute on DOM-elements. When I write the front-end markup I avoid at all costs to use the style-attribute, but when I animate something, or just plain and simple toggle the visibility of something in javascript I might use the style-attribute. If I wouldn't allow the browser to parse the style-attribute, I'd have to create more CSS classes than needed, which in turn might jeopardize the readability of the code (And would be impossible when animating a standard CSS property like margin-top).

There's two ways to implement the header depending on how you need to use it. The first one, which is the recommended way, is to add it in your webserver config. This means that the header will be sent no matter what. It's important to note that if you use some kind of platform like wordpress, magento, drupal etc. you can't generally control the admin area, and most of these platforms render script-tags in the markup. To circumvent this you'll need to add the header for all pages, and then remove the header from the pages that can't support CSP.

# Example of adding the CSP header for a wordpress installation

# Set the header on all pages under the path /
<Location />
	Header add Content-Security-Policy "default-src 'self'; img-src *; script-src 'self'; frame-src *; object-src *; style-src 'self' 'unsafe-inline'; connect-src 'self' http://www.youtu; font-src 'self';"

# Remove the header for the admin area.
<location /wp-admin/>
	Header unset Content-Security-Policy

# Remove the header for the login page.
<FilesMatch "wp-login.php">
	Header unset Content-Security-Policy

The other way to add the header is through your application. The main benefit of adding it programmatically is that you'll be able to control when the header is outputted (or if you don't have access to the webserver configuration). The main disadvantages is that if you use some kind of cache, it might remove the header (because the application code won't be run, a static cache file will be served instead), the other problem is that your code might throw an non-recoverable error before you're able to set the header, leaving the page vulnerable.

// Retrieve script filename (wordpress)
global $pagenow;
// Set CSP header, unless we're in the admin area or the page is wp-login.php
if(!is_admin() || $pagenow != "wp-login.php") {
	header("Content-Security-Policy: default-src 'self'; img-src *; script-src 'self'; frame-src *; object-src *; style-src 'self' 'unsafe-inline'; connect-src 'self' http://www.youtu; font-src 'self';");


At time of this writing not all browser implement support for content security policy (another reason why this isn't a replacement for cleaning data). In order to protect as many modern browsers as possible I'd recommend using the standard header, Content-Security-Policy, as well as the webkit version, X-WebKit-CSP, the syntax is pretty much identical for both of them. Chrome, Firefox, Opera and Safari have implemented one or both of those headers. Internet explorer does have a partial support for CSP since IE10, but the IE specific header doesn't use the same syntax as the standard header, and it's not as configurable, I would recommend against using it until it supports CSP fully.

Benjamin Horn
Benjamin Horn
Developer at Bazooka
A Finnish-German full-stack developer who's worked with a multitude of different technologies throughout the years.
Vaasa, Finland