Content security policy (CSP) is an immensely powerful tool for protecting our websites by providing browsers with the expected type, behavior and origin of its content. A good CSP is based on a white-listing approach, disallowing everything except explicitly allowed content. CSP is not without some pitfalls, and requires careful deliberation before deployment.
Tl;dr - Be careful what you whitelist in your CSP. Many public CDNs provide outdated and insecure libraries. Google hosted libraries (ajax.googleapis.com) is a really bad offender. If you must use public CDNs, prefer smaller ones which focus on a single project.
Also, don’t rely solely on CSP for protection against XSS and other injection attacks. Use it to harden your other defenses, including input validation and output encoding.
CSP fail
Lets say we have a website which displays user generated content. We are aware of the dangers of XSS and that CSP is a great way to prevent it. Our website only uses local resources, except for jQuery, which is loaded from Google’s CDN. So we come up with the following policy:
Content-Security-Policy: default-src 'self' ajax.googleapis.com
This disallows inline javascript and only allows content from the originating domain, along with the Google CDN. Bulletproof, right?
The next thing that happens is users report strange behaviors with our website. Some are redirected to other sites containing ads and malware. Others have noticed mysterious flash objects on the page.
What went wrong?
In our hypothetical example above we made a few mistakes.
-
Relying solely on CSP for XSS prevention. We should have used input validation and output encoding to prevent injection attacks, and used CSP as an additional layer of defense.
- DON’T rely on CSP as the only defense against injection attacks.
- DO validate input based on a white-listing approach.
- DO encode output to prevent HTML/JS/CSS injection.
-
Whitelisting Google’s CDN. Ajax.googleapis.com contains a lot of javascript libraries, and various versions for each library. Some of these contain security vulnerabilities which can be a vector for XSS-ing sites. We allowed this CDN so we could load jQuery but we can’t at the same time disallow other libraries hosted there. For example, the following tag is allowed by our policy but loads an insecure library and executes user-supplied code (source and explanation).
<script src=//ajax.googleapis.com/ajax/services/feed/find?v=1.0&callback=alert&context=x></script>
-
Unnecessarily permissive policy. Although it doesn’t open up a lot of possible sources of content, it doesn’t restrict allowed content types either. Don’t expect to ever run Flash or other thick-client objects on your website? Disallow them through CSP. Not planning on using webfonts? Dissallow them through CSP. We can always change the policy if our requirements change.
How do we fix it?
Learning from our mistakes, we go from this CSP allowing all content types, both local and from Google’s CDN:
Content-Security-Policy: default-src 'self' ajax.googleapis.com
To this one, only allowing the content we actually use (and replacing Google’s CDN with jQuery’s):
Content-Security-Policy: default-src 'none'; script-src 'self' https://code.jquery.com; img-src 'self'; connect-src 'self';
One last thing - do make use of the report-uri functionality to catch policy violations in browsers. If you are accidentally preventing your visitors from using your site, you definitely want to know about it. Report-uri.io is a great resource for this.
Photo credit: Lake Skeifan, Veiðivötn, Iceland. Own photo.