September 09, 2018

Many sites use a content delivery network (CDN) to serve static assets such as JavaScript, CSS, and images. This makes web browsing faster, because these assets are cached by and delivered from data centers that are geographically close to the end user.

Sometimes, assets are just loaded from a CDN, because it's convenient. Package creators advertise with a CDN link to show you how easy it is to implement their great library. "Just add this single script tag to the head of your page and you're good to go".

But for the developer, importing a library should be much more than the statement above. At least you:

  • make sure the library is well-known
  • the link refers to a popular CDN
  • know what you're importing

So let's talk a bit about security.

Cross-Site Scripting (XSS)

Companies that take security seriously, already protect their users against cross-site scripting. In a nutshell, with XSS, an attacker injects a malicious script into a trusted website from another location.

Let's say a well-known company loads the popular library jquery.min.js from a trusted CDN cdn.example.com.

<script src="//cdn.example.com/libs/jquery.min.js"></script>

Millions of users have a great experience, because using the CDN makes the site so damn fast.

Bad CDN

But what if the CDN get's hacked, and the contents of the library go malicious?

The hacker can change the source code of jquery.min.js to load malicious javascript from their own server my.oh-so-bad-server.com. This means that if we did not protect ourselves with XSS, the external library from the bad server would be running right now.

Another option for the hacker is changing jquery.min.js to include all the malicious code they want to run on the website. Since they will not use their own server to host the malicious code, there is nothing wrong in the website's perspective. Everything is loaded from a trusted CDN, right?

Subresource Integrity

So now we have a website with millions of users running malicious code from a "valid" library on a "trusted" CDN. Yeah, more protection is needed!

To make sure valid code is executed by the website, Subresource Integrity (SRI) can be used. It basically means that from the original contents of the library, a hash value is calculated, and checked against the pre defined hash.

The script included on the website will now look like this:

<script src="//cdn.example.com/libs/jquery.min.js" 
    integrity="sha384-I6F5OKECLVtK/BL+8iSLDEHowSAfUo76ZL9+kGAgTRdiByINKJaqTPH/QVNS1VDb" 
    crossorigin="anonymous">
</script>

The users browser loads the script from the CDN, and calculates the hash value. If the value does NOT match the value in the integrity-attribute, execution of the code in jquery.min.js is blocked. The crossorigin-attribute enforces a CORS-enabled load. The anonymous value means that the browser should omit any cookies or authentication that the user may have associated with the domain. This prevents cross-origin data leaks, and also makes the request smaller.

Without CDN

All of the above also applies to sites that do not use a CDN. When all assets are loaded from the web server itself, and the web server get's hacked, then there is a similar situation.

In that case much more can go wrong, but at least your javascript assets will be checked.

HTTPS?

That should be HTTPS!!! Although Subresource Integrity works on both HTTP and HTTPS, you should never serve your page over plain HTTP. In plain HTTP, an attacker can see your traffic flow by unencrypted, and can easily modify your HTML by removing the integrity-attribute. Always use HTTPS!

More info

Do you want to test it out yourself, by adding integrity hashes to you assets? You can generate them at srihash.org. The SRI specification can be found at w3.org. And if you are building a GatsbyJS website, check out the gatsby-plugin-sri plugin I've created to generate the hashes automagically for you.