Script Integrity

There was lots of news recently about the polyfill.io project. This was a popular project a few years back, as you could just link it up and it would polyfill whatever web platform features it needed to based on feature testing and UA sniffing. If you loaded the script up from their domain, as the homepage suggested you did, you might have been served some malicious code.

Users are being redirected to sports betting websites or adult domains, likely based on their location, the threat intelligence firm said.

SecurityWeek

The project is offline now, but as of June 1st, 2024 this is how they suggested hotlinking.

When you link to any resource on a domain you don’t control, it’s a risk. It can be a calculated risk. For example, tons of websites load a script from google.com for their Google Analytics. Or load fonts from adobe.com for their typography. Those tend to be trusted sources, but that trust is entirely up to you.

It’s entirely possible that a resource you link to from a third-party disappears or changes. Worst case: changed maliciously, like we’ve seen here. You can bet your ass Google and Adobe don’t load any resources, especially JavaScript, from third-party domains they don’t control.

Protection Against Changes

There is a web platform feature that can help against a third party changing the code they are providing. It’s the integrity attribute on <script> or <link> elements (which are rel="stylesheet"rel="preload", or rel="modulepreload"). It’s called “Subresource Integrity”, to name it correctly.

So for example…

<script
  src="https://third-party.com/script.js"
  integrity="sha384-[hash value here]">
</script>
Code language: HTML, XML (xml)

Now if script.js changes at all, even just a single character, the browser will refuse the execute the script (or stylesheet). Safety!

Some responsible third-parties will offer this directly, which is nice to see.

instant.page versions their script and provides the integrity attribute for safety.

CDNjs makes this a part of the default code you copy and paste.

I particularly like how the protection integrity provides protects against some possible middleman attacks as well. Say your weird hotel WiFi intercepts requests and changes the response (I’ve seen it!), this will prevent the tampered-with script from executing. Uh, well, unless they tamper with the HTML and change the attribute value, which is certainly possible as altering HTML is exactly what invasive in-app browsers do. Still, most security is a do-as-much-as-you-can game and this helps.

When Not to use Script Integrity

The above two examples are kinda perfect as the scripts they are linking to are versioned. They are published with that exact version and that version will never change. In this case, it’s by virtue of strong conventions. When public libraries update, the code at that version is locked in stone. Any changes cause a version update. If the code of a version changes without a version change, that would be highly suspicious, probably malicious, and a great situation for <script integrity="..."> to block. Plus, the major places where libraries are published (i.e. npm) literally don’t allow changes to published versions anyway.

While integrity is often an excellent idea, it’s specifically for versioned resources that don’t change. You would not use it if:

  1. You’re linking to a resource that is OK to change
  2. You’re providing a resource that you intend to change

Perhaps you’re using a script from some analytics service provider. It’s pretty likely that they don’t use the integrity attribute when they give you the script they want you to add. That’s likely because they want to be able to actively work on this script and what it does without having to tell every single customer they need to update the version of the script otherwise it will stop working.

Ironically, a company being able to update a script on the fly means they could potentially patch a security problem.

Would Script Integrity have Stopped the Polyfill.io Problem?

Maybe! It depends what they did. Some reporting said that:

The malicious code dynamically generates payloads based on HTTP headers, activating only on specific mobile devices, evading detection, avoiding admin users and delaying execution. The code is also obfuscated.

So: yes. If what was happening is that the initial response/content of the script was changed from what it was when the integrity value was created, it would have prevented these malicious changes from running.

But they could have evaded this kind of stoppage.

They way that polyfill.io worked already was that it loaded additional content — the polyfills themselves — as needed. That additionally-loaded content could have been changed to be malicious and would not have been subject to subresource integrity. I’m not trying to make bad guys lives easier here, just sayin’.

How To Do It Yourself

You don’t need a third-party to hand you this attribute to use it. It’s just a web platform feature, so you can use it if you want to.

Maybe the easier way is to go to the SRI Hash Generator website, pop in the URL of a resource you want to protect, and hit the button to get the code:

NOTE: I’ve seen plenty of places recommend this site, but when I used it, it didn’t seem to work for me. For example, the exact code above:

<script src="https://assets.codepen.io/3/log-something.js" integrity="sha384-ZTxYWn5UcuOi7Xt1wWg/QFcsZJEXjJg7tgCnVbx0+ssBvbi1Sw/hY5GzPGHSD1NW" crossorigin="anonymous"></script>Code language: HTML, XML (xml)

That fails for me in Chrome. I had to let Chrome error with a message in the console, which provides the correct hash in the error message, and use that corrected hash for it to work:

<script src="https://assets.codepen.io/3/log-something.js" integrity="sha384-H7W+IxM2qbwMSJYRqmcgYXq0TRko/BIFtURUjh2QG0Z8MM9I6t2f4n+2BOCmbjvD" crossorigin="anonymous"></script>Code language: HTML, XML (xml)

So… your milage may vary. Here’s the proof on all that.

It’s also worth noting that CORS is involved. Without that crossorigin="anonymous" on there, I was seeing a CORS error with that code above, even though we do serve assets with a Access-Control-Allow-Origin: * header. Shrug — websites are hard.

It's time to take your JavaScript to the next level

Leave a Reply

Your email address will not be published. Required fields are marked *

Did you know?

Frontend Masters Donates to open source projects. $363,806 contributed to date.