Most sites on the Internet these days load some kind of content from a CDN, usually JS and CSS. Whilst this comes with great performance boosts and savings on bandwidth, we're trusting that CDN to load content into our pages, content that could possibly be harmful. Until now, we had no way to verify the content we were loading from the CDN was actually what we expected, it could have been altered or replaced. SRI allows us to check the integrity of the JS or CSS to ensure it's exactly what we were expecting.
Consensual XSS
All of my sites fully utilise a CDN to deliver major frameworks or libraries that I use on them. There's https://ajax.googleapis.com for JQuery, https://maxcdn.bootstrapcdn.com for Bootstrap and https://cdnjs.cloudflare.com for countless other JS libraries that I depend upon. I'm depending on these sites to load script into my pages which is a big thing. If one of these sites were to be compromised, an attacker could switch out the content of the files I'm being served and launch an XSS attack across thousands or even millions of sites on the web. Using a CDN is consensual XSS and we're trusting the CDN not to do anything untoward.
Subresource Integrity
Rather than trusting a 3rd party not to do anything untoward it'd be far better to actually verify that they're not doing anything nasty, and that's exactly what SRI allows us to do. In short, SRI allows us to instruct the browser to perform an integrity check on an asset loaded from a 3rd party. By embedding the base64 encoded cryptographic hash digest that we expect for the asset into the script or link tag, the browser can download the asset and check its cryptographic hash digest against the one it was expecting. If the hash of the downloaded asset matches the hash that we provided, then the content is what we were expecting to receive and the browser can safely include the script or style. If the hash doesn't match then we know we can't trust the data and it must be discarded.
The integrity attribute
We can instruct the browser to check the integrity of externally loaded content by including the integrity
attribute into <script>
or <link>
tags. This attribute will contain the base64 encoded versions of the cryptographic hashes we expect for that particular asset.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha256-ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" integrity="sha256-8EtRe6XWoFEEhWiaPkLawAD1FkD9cbmGgEy6F46uQqU=" crossorigin="anonymous">
Alongside the SHA256 hash you can also specify the SHA512 hash and the browser will select the strongest hashing algorithm that it supports for the integrity check.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha256-ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM= sha512-7aMbXH03HUs6zO1R+pLyekF1FTF89Deq4JpHw6zIo2vbtaXnDw+/2C03Je30WFDd6MpSwg+aLW4Di46qzu488Q==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" integrity="sha256-8EtRe6XWoFEEhWiaPkLawAD1FkD9cbmGgEy6F46uQqU= sha512-/5KWJw2mvMO2ZM5fndVxUQmpVPqaxZyYRTMrXtrprsyQ2zM0o0NMjU02I8ZJXeBP trmrPO4IAyCCRsydG0BJoQ==" crossorigin="anonymous">
In addition, it's also worth noting that you can specify multiple hashes of the same type if, for example, you were expecting different content to be served for the request.
Generating the SRI hash
To make adopting SRI a little easier, I've made a tool to generate the SRI hash value and create the script or link tag for you!
Simply navigate to the SRI Hash Generator, paste in the URL of the JS or CSS file that you want to generate a SRI hash for and hit Hash! The page will fetch the resource, generate the SHA256 hash value and output the appropriate script or link tag for you to copy and paste. It's that easy. Now, using the example in the image above, if somehow the content of the JS file were to be maliciously altered, the browser would refuse to load it as it can detect the change.
From the command line
You can also generate the necessary hash from the command line using OpenSSL.
wget https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
cat jquery.min.js | openssl dgst -sha256 -binary | openssl enc -base64
ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM=
The wget command simply fetches the file and stores it locally. From there the cat command outputs the file which is piped into OpenSSL that is going to run it through the SHA256 hash and give us the digest in binary form. That is then piped into OpenSSL again to base64 encode the output. The resulting string is the value that is needed for the integrity attribute.
The crossorigin attribute
The crossorigin attribute in the script or link tag may be new but all it does is instruct the browser to make a CORS enabled request for the asset. Cross-Origin Resource Sharing is a little out of scope for this blog but you can read more about it. Due to restrictions in the spec on which assets are eligible for integrity checking, we must include this attribute for integrity checking to work. There are 2 possible values for the attribute and they are "anonymous" and "use-credentials". The anonymous value is used as this does a plain HTTP request and no credentials are sent. That means no cookies, no X.509 certificate and no HTTP Basic Authentication.
Browser Support
As with any relatively new security feature, browser support isn't quite where we'd like it to be. SRI is supported in Chrome 45+ and Firefox 43+. You can vote for Edge to support SRI and there's also a bug open to implement SRI in WebKit. Whilst Chrome and Firefox support allows us to protect the majority of visitors, it's worth noting the lack of support in other browsers.
Real world application
GitHub recently announced that they will be rolling out support for SRI and suggested that it could have played a vital role in helping to mitigate the recent Great Cannon attack against them. A malicious attacker replaced a JS file in legitimate web traffic with a script that was designed to attack specific websites including GitHub. With the browser having no way to verify the script file it received, it dutifully began attacking GitHub. The scale of the attack was enormous but SRI would have put a stop to the attack before it even had chance to start.
Backup plan
Earlier in the specification there was the ability to specify a 'backup plan' if you will. This would stop changes in the file breaking your page and allow you to load from another location if desired.
<script src="https://scotthelme.co.uk/js/jquery2.1.3.min.js" noncanonical-src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha256-ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM=" crossorigin="anonymous"></script>
This feature has been pushed to V2 so should not be used. By specifying the noncanonical-src the browser will try to load that asset first. If the SRI check fails, it will fall back to the src value and not apply integrity checking. Even though this feature isn't supported, we do have another backup option for JQuery.
<script>window.jQuery || document.write('<script src="path/to/your/jquery"><\/script>')</script>
This snippet of code will check to see if JQuery has been loaded and if not, will load another locally hosted version. If the SRI hash check fails for any reason, your site won't completely break. Please note though, you should include this after you have tried to load the external script and not before.