Practical Prevention of Web Shenanigans With Content Security Policy
Use CSP to help protect your site
Use CSP to help protect your site
An attacker visits a site and registers a <script> tag as their username. When a regular user visits the site and sees the attacker's username, the script tag is inserted into the page and the regular user's session is compromised by the attacker. The attacker can now grab passwords, payment information, and other private data from the regular user. Shenanigans occur.
This is Cross Site Scripting, or XSS—one of the OWASP Top 10 vulnerabilities that continues to plague web applications.
Modern templating languages escape variables by default to prevent this, i.e.: {{ someVariable }} escapes someVariable by default. However:
There's still a large amount of ways XSS can happen.
If any of these isn't covered by the template language, an attacker can inject code.
A better solution is Content Security Policy (also known as BATSHIELD), created by Mozilla. It's a well supported HTTP header that says which origins the site allows for images, scripts, styles and other types of content. Anything else is blocked by the browser. If you're not familiar with CSP, read this excellent introduction from Mike West from the Chrome team (also known as my quest.
Our own site recently adopted CSP. Our goals were as follows:
The basics were easy:
<script src="js/something.js">.Some parts were more difficult: here's how we handled them.
Third party APIs are a common part of the modern web. They're used for payment processing, font hosting, database as a service apps, showing happy tweets from customers, and a lot more besides.
So a large chunk of the work for CSP isn't in creating a policy for your own code: it's discovering, merging and managing policy requirements for third party APIs.
Our site uses the following third party APIs. Most did not have official documentation on CSP:
| API | Documented CSP Policy |
|---|---|
| Google Fonts | No documented policy |
| Mixpanel | No documented policy |
| Ractive.js | Documented policy |
| Stripe | Documented policy |
| Twitter oembed API | No documented policy, but some CSP notes |
| Typekit | Documented policy |
| Stormpath | No documented policy |
We patched the ractive docs ourselves shortly before this article. Otherwise, only Stripe and Typekit provided full policies, detailing the individual settings required for their services. Typekit uses inline styles for font events, but explained the requirement clearly in their documentation. We're happy to go without font events, however the CSP violation would still appear as on the console, so we've allowed inline styles until we've finished talking to Typekit.
Stripe specifies an exact, conservative policy, which is great, although they forgot a domain - they use q.stripe.com for error reporting. Their CSP requirements are also slightly hidden in an article about PCI DSS. On the other hand, their competitor Braintree just has a list of domains and wouldn't provide info about which domains are used for scripts, images etc. when asked.
So we built the CSP policy for each third party API, starting with official policies where possible, and then testing with CSP's report-only mode.
However we still wanted to keep the individual third party API policy requirements separate from our base policy. For example, we're moving from Google Fonts straight over to Typekit. When we get rid of Google Fonts, we want to be able to simply remove Google Fonts from our CSP policy.
We wanted something like this:
var policy = cspByAPI(basePolicy,
[
'twitter',
'mixpanel',
'googleFonts',
'stripe',
'typekit',
'ractive',
'stormpath'
]);
I.e., produce a merged, sorted, non-redundant policy from the named entities. There are several open-source CSP policy merging tools available on npm that can help with this approach.
It's not particularly earth-shattering code - however we do think it represents an excellent common-sense approach to handling CSP for modern websites. It should also save you a bunch of manual testing and research if you use any of the libraries mentioned.
Official policies are used wherever they're made available, and all are tested in a production app. Pull requests are welcome if you'd like to add policies for additional APIs, and if you'd like to port to Python / Ruby / Elixir / Go / whatever else you're more than welcome.
A common technique to include data from the backend into the front end is to include something like this in a backend template:
<script>
var serverVars = {{{ serverVars }}}
<script>
Where {{{ serverVars }}} is a JSON-encoded set of variables.
Doing this with CSP requires the use of script-src unsafe-inline, which defeats a large part of the purpose of using CSP.
Instead, we made a non-executable <script> tag, eg:
In our backend template:
<script class="server-vars" type="application/x-configuration">
{{{ serverVars }}}
</script>
The <script> tag here is not JavaScript, it's just data.
Then in a script tag on our server:
var serverVarsElement = document.querySelector('.server-vars')
if ( serverVarsElement ) {
window.serverVars = JSON.parse(serverVarsElement.textContent);
}
This allowed us to get backend data into the frontend without inline JavaScript.
Browsers that encounter CSP violations will POST the details of the violation to the configured CSP report URI. We originally just logged these, then collected logs directly from Linux's journalctl, but a colleague mentioned report-uri.io, a new web service for collecting and summarizing CSP log information.
report-uri.io is new but very promising: it's a great way to:
With report-uri.io we were able to find things we'd missed in our original policy (StormPath uses bootstrap) as well as find common violations - now we've finished our policy, violations are mainly from browser extensions.
Some thoughts after implementing CSP:
eval for Ractive. We're talking with Typekit to see what our options are: either font events that work with CSP, or being able to disable font events completely. Ractive is already looking at removing the eval requirement: there's a performance hit, but it may be worth it.We hope that was helpful and good luck with your CSP endeavors!
CSP is an important layer of defense, but it works best when combined with other security measures. Our Expedited WAF can add security headers including CSP automatically, and provides additional protection against XSS and other OWASP vulnerabilities.