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.
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.
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:
- You’re linking to a resource that is OK to change
- 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.