Subresource Integrity Checking and Content Security Policies

Clare Lindley
5 min readApr 22, 2019

--

This post is part one in a series of write-ups of the Pluralsight course ‘Play by Play: Modern Web Security Patterns‘ by Troy Hunt and Lars Klint

The course can be found here: https://app.pluralsight.com/library/courses/play-by-play-modern-web-security-patterns/table-of-contents

I’m really enjoying it and thought it would help the knowledge to sink in if I wrote about it. The first module I watched talked about some of the pitfalls of including external JavaScripts, and some interesting defenses against cross-site scripting.

The problem with including external JavaScript

is that if someone manages to modify that, you are, of course, unwittingly embedding their malicious script on your website — giving someone else control of your site and your visitors’ browsers. In the Pluralsight module, the example used is last year’s hack of Browsealoud — the popular web screen reader software — where cryptocurrency mining code was inserted into the browsealoud script file and run on every machine that visited the compromised sites.

The defenses

First off, I always like to explain things in my own words, because I find it easier to remember. A lot of web app security attacks and defenses don’t seem to be logically named to me. XSS, or ‘Cross Site Scripting’ for example, doesn’t even work as an acronym! I always have to do the mental gymnastics to translate that into: ‘an attacker running their own scripts on your site’. So I’ll do the same for the 2 solutions for anyone who’s as jargon-averse as I am.

The first defense is so simple, and so smart — and so damn OBVIOUS once you know about it, you’ll wonder why you haven’t heard of it before, and it’s something called ‘Subresource Integrity Checking’, or ‘comparing hashes of yours and theirs to make sure the script is legit’ (told you it was simple!)

In practise this means adding the ‘integrity’ attribute to any script tags that pull in third party libraries — the value of which is a sha384 hash of the file!

In the pluralsight video, the example given is loading jQuery (the script) from Cloudflare’s CDN (the third party) on Troy Hunt’s https://haveibeenpwned.com/ site:

The integrity attribute with the hash of the external script file coming from Cloudflare

If the file changes, and those 2 hashes don’t match any more, the browser will block the script — like so:

Error in the Chrome console when the 2 hashes do not match

(An aside: How Troy demoed this — which I wanted to do, but now feel is a blog post in itself — is with a debugging proxy called Fiddler that allows you to modify the script sent back to the browser, so you can really see the browser calculating the hash and rejecting the script in real time. I didn’t like the look of Fiddler for Mac though, it looked too Windows-y. And while there’s another tool called Charles for Mac, it required a fiddly bit of set up to get proxying HTTPS requests so…. one for another day, methinks — for now I settled for taking screenshots from Pluralsight) If anyone knows a good Mac-equivalent of editing responses with Fiddlerscript, please let me know in the comments.

Limitations of SRI

SRI works well when you’re importing an external script that’s versioned, because it’s not going to change very often — so you can rely on a difference in the hashes being an indicator of something fishy going on. However, when you’re using JS as an external software service not a library, it’s a bit different. You can’t host the file yourself and you can’t, and should not, depend on one version of the software. So what can you do in this case?

Content Security Policies to the rescue!

Another bit of jargon — a Content Security Policy is simply a whitelist of the sites that you want to allow to provide content to your site. If you’re pulling in a service from a given domain, you can restrict scripts being loaded by it to just that domain (and of course, anything else that you need to run your site). It’s a HTTP response header, to get this work you need to configure your web server to return the Content-Security-Policy HTTP header

Then the browser will reject anything that’s not on that list. Here’s an example of the error in the Chrome console when a request is blocked by a Content Security Policy:

Just be mindful of being too lenient with sites that load content from other sites — then you’re back where you started and might as well be running no CSP at all!

Workarounds to add CSPs when you can’t control the Headers

You don’t just have to add a CSP via a response header! You can also add it via a metatag — so if you’re running a blog and have control of the templates, or your platform allows you to edit metatags, you can add a CSP.

A reminder about browser compatibility

And of course whether or not we can use any W3C feature like the ‘integrity’ attribute is going to be dependent on a particular vendor’s implementation of that feature — and this is no exception. Always check whether there is browser support: https://caniuse.com/#search=subresource%20Integrity

To summarise

Use SRI if it’s a versioned library that doesn’t change, e.g. jQuery

Use CSP if the script is a software service that you cannot host and is changing often e.g. Browsealoud

These 2 defenses complement one another — ideally you want to run them both together.

Thanks for reading, tune in next month for more web app security and JS goodness!

--

--

Clare Lindley

Software Engineer writing about JavaScript and Web Application Security.