Content security policies (CSPs) can be both a blessing and a curse. A blessing because they can do neat stuff like my recent piece on upgrading insecure requests yet a curse because they can also do screwy things like break your site. Now in fairness, the breaking bit linked to there was more because of Safari's screwy implementation than because of the CSP spec itself, but that brings me to today's post on yet another screwy browser implementation of CSP. This time, it's Chrome's turn and it didn't just cause content to be blocked, it actually cost me money. Let me explain.
I have a donate page on Have I been pwned (HIBP). I honestly didn't expect people to give me money for something I provide for free, but it turns out plenty of people are happy doing it and obviously, I'm happy when they do! A little while back, I had a few messages from people saying "Hey, your donate page doesn't work" to which I gave the tried and tested standard response of "Works on my machine". But it was more than that because I was seeing donations from other people come in so if something was broken, it must have been a real edge case. It wasn't until someone actually showed me their console output in Firefox that the penny dropped. Here's what happened:
Let's say you decide to shout me some beer because running this service is thirsty work:
You hit the "Donate" button then leave me a friendly message:
So far, so good. Now you hit the "donate now via PayPal" button which gives you this:
This is all fine, now let's try it in Firefox:
Then hit the same button and... here's what you see:
Huh. You must not have clicked it, I'm sure if you click it again it will be just fine:
Alright, this is weird, let's check the console:
This is talking about a form action, so let's look at what's happening on the page form wise:
At first glance, I blamed Firefox. I thought I'd properly added PayPal to the form action directive therefore Firefox was to blame, right? On a whim, I pinged my mate Scott Helme who's done many, many cool things with CSPs in the past. And then we actually took a look at the source CSP:
content-security-policy:default-src 'self' https://www.google.com;script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com https://www.gstatic.com https://apis.google.com https://www.google-analytics.com https://cdnjs.cloudflare.com https://js-agent.newrelic.com https://bam.nr-data.net;object-src 'none';style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com;img-src 'self' https://www.google.com https://www.google-analytics.com https://ssl.gstatic.com;media-src 'none';font-src 'self' https://cdnjs.cloudflare.com;child-src 'self' https://www.google.com;form-action 'self' https://accounts.google.com;frame-ancestors 'none';report-uri https://haveibeenpwned.report-uri.io/r/default/csp/enforce
Here's the important bit:
form-action 'self' https://accounts.google.com;
Where's PayPal? And if there's no PayPal, how come Chrome is able to submit the form? Because Chrome is broken, that's why. I added the form-action directive on the 25th of May and the penny didn't drop on what was happening until the 16th of July so in other words, I'd just gone more than 7 weeks breaking donations from any browser that wasn't Chrome and that actually recognises the form-action directive (so Internet Explorer, was, uh, "fine"...)
Scott reached out to the Chrome folks and submitted a bug report on July 21. Last week, it was finally fixed in version 55.0.2883.75 which includes this:
[$N/A][630332] Low CVE-2016-5225: CSP bypass in Blink. Credit to Scott Helme (@Scott_Helme, scotthelme.co.uk)
Which is great, because it means that Chrome now breaks as expected:
Now let's be clear about this - I screwed up. I should have added PayPal to the CSP and I didn't. I also didn't monitor my CSP reports which are submitted to Scott's free CSP reporting service, report-uri.io. Regardless of my own shortcomings on this, I wanted to write about both the ease with which you can screw these things up and the continual idiosyncrasies of browsers in implementing the spec. Which brings us to this:
Be very careful with CSP! You can easily break your things without realising it and you need comprehensive testing and monitoring to avoid problems.
On the monitoring front, part of the problem with CSPs is that the reporting is very noisy. I just took a quick look at my reports in report-uri.io and saw the usual plethora of things that require no action on my part: blocked URIs of "data", plugins attempting to load content from non-white-listed URIs and requests from adware-riddled machines.
CSPs are awesome, but exercise extreme caution when using them. The whole purpose of a CSP is to stop requests being made under certain conditions and merely adhering to the spec won't ensure that doesn't happen when it shouldn't. If you'd like to know more about CSPs, check out my Pluralsight course on browser security headers.