I love it when a whole bunch of different bits play really nice together, especially when it’s making things more secure. Today I decided to properly implement a content security policy (CSP) on Have I been pwned? (HIBP) and managed to tie in a whole bunch of nice bits to create what I reckon is a pretty neat implementation.
Firstly, if CSP is new to you, go and read Scott Helme’s overview which is excellent. The tl;dr version is simply this: CSP lets you define via HTTP response headers what the browser should be able to load and parse and from where. If nasty, unexpected things like XSS happen, the browser will adhere to the CSP rules which put a stop to many of the popular approaches used in an attack like this such as embedding external resources.
Here’s how I put it all together today.
Using NWebsec
Fellow developer security MVP André N. Klingsheim has a great open source project in NuGet called NWebsec. I used this already to configure things like HSTS and X-Frame-Options headers and it also does a great job of setting up CSP for you. The trick is to work out what you want to trust in a white-list style approach that allows only the things you really need to be embedded in your valuable web asset.
Here’s what I ended up with:
<content-Security-Policy enabled="true"> <default-src self="true" /> <script-src unsafeInline="true" unsafeEval="true" self="true"> <add source="https://www.google.com" /> <add source="https://www.google-analytics.com" /> <add source="https://cdnjs.cloudflare.com" /> </script-src> <style-src unsafeInline="true" self="true"> <add source="https://cdnjs.cloudflare.com"/> </style-src> <img-src self="true"> <add source="https://az594751.vo.msecnd.net"/> <add source="https://www.google.com"/> <add source="https://www.google-analytics.com" /> </img-src> <font-src> <add source="https://cdnjs.cloudflare.com"/> </font-src> <object-src none="false" /> <media-src none="false" /> <frame-src none="false" /> <connect-src none="false" /> <frame-ancestors none="false" /> <report-uri enableBuiltinHandler="true"/> </content-Security-Policy>
This mostly boils down to CloudFlare’s CDN supporting things like Bootstrap and jQuery plus I’m using an Azure CDN for some images and then of course some inline CSS and JavaScript. I could get stricter on these and either rejig the site to remove that dependency or even whitelist a hash of the offending text blocks, but that might be something for later.
That last node on report-uri is important too and if you read Scott’s overview you’ll see it’s a means of the browser reporting any violations. NWebsec makes it easy to send these to a handler and then catch the event it raises in Global.asax like so:
protected void NWebSecHttpHeaderSecurityModule_CspViolationReported(object sender, CspViolationReportEventArgs e) { var report = e.ViolationReport; var serializedReport = JsonConvert.SerializeObject(report.Details); new ReportCspViolation().SaveReport(serializedReport); }
You’ll see here I’m saving a report – what happens in that guy?
Saving reports to Azure Table storage
I’ve written a lot about Azure Table Storage in the past and it’s a great way of really cheaply storing large whacks of data in a super easy fashion. By hooking this into the auto-generated reports from the browser when something goes wrong, I get a bunch of results like this:
I used a partition key of yyyy-MM-dd and a row key of HH-mm-ss-fff-[guid] which is enough to be able to read through things sequentially which is fine for these purposes. The contents of those rows looks something like this:
{ "blocked-uri":"https://js-agent.newrelic.com", "document-uri":"https://haveibeenpwned.com/", "effective-directive":"script-src", "original-policy":"default-src 'self';script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com https://www.google-analytics.com https://cdnjs.cloudflare.com;style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com;img-src 'self' https://az594751.vo.msecnd.net https://www.google.com https://www.google-analytics.com;font-src https://cdnjs.cloudflare.com;report-uri /WebResource.axd?cspReport=true", "referrer":"https://www.google.com.au/", "status-code":"0","violated-directive":"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com https://www.google-analytics.com https://cdnjs.cloudflare.com", "source-file":"https://haveibeenpwned.com/", "line-number":"4", "column-number":"2714", "script-sample":"" }
Ah – so I’ve not white-listed NewRelic as a valid script source. But it all worked on my machine!! Of course it’s only once I deploy into production that New Relic’s bits get added so I didn’t see this when initially testing. So I fixed this and still got reports, but this time, I checked out the browser console in the live system and saw this:
Ah, another New Relic URL. Fix, deploy, done.
But I actually knew about this issue before even checking out Table Storage courtesy of Raygun. Let me explain how.
Receiving policy violation alerts via Raygun
I love Raygun for tracking pesky errors and I’ve written about it many times before now. One of the things I love is the ability to flick exceptions over to the service so that they’re properly logged. Here’s what I added to my method which handles a new CSP report from the browser:
private void SendMessageToRayGun(string violation) { var ex = new Exception(violation); new RaygunClient().Send(ex); }
There is quite likely a more “proper” approach of creating a Raygun message of this nature, but it certainly works effectively as above. What it meant was that as soon as I fixed the first New Relic policy I mentioned earlier, I then started getting these emails instead:
That’s enormously handy as it lets you get on top of things really quickly.
Of course the other value on a longer term basis is that it means I’ll start getting notifications if any funny business is going on in terms of someone attempting to exploit the very things that the CSP is there to protect people from. In all likelihood though, I’m going to be seeing these more often when I’ve done something like with New Relic just now and not properly whitelisted a new dependency I’ve just added. Same deal though – I want to know about this early so that I can get it sorted quickly. Raygun is a great way of achieving that.
Summary
Configuring CSP correctly is a great security measure, but you can also screw it up really easily and block the wrong things. I made this change out of peak hours whilst I was going to be around for some time and fortunately none of it actually broke the end-user experience. Definitely log any violations and I strongly recommend the Raygun style approach of notifications too. If you’re the extra cautious type, use the “Content-Security-Policy-Report-Only” header and don’t break things for people if there’s a violation (which, of course, is what CSP is meant to do!) just while you make sure everything is properly configured.
A word of caution though – early on I started seeing a lot of reports for assets that weren’t on the whitelist and I definitely wasn’t including in the site. For example, there were a number for http://compare.buyhatke.com which I suspect comes via either nasty browser add-ons or outright malvertising (and there’s arguably not much difference between those two). Whilst it’s actually kinda insightful as to what’s going on with your site in other peoples’ browsers, this can also get pretty noisy. I may end up excluding certain patterns either from being stored in Table Storage or just being sent out via Raygun. But hey, at the very least it’s nice to know that the CSP header is now helping to keep some rubbish out of my visitors’ browser!
Do get this set up though, CSP is a powerful features that’s easy to access and could save you from some real grief in the future.