This morning, soon after I got to work, one of my favorite coworkers sent me a direct message on Slack. They had heard a lot of discussion yesterday in our internal #front-end-devs channel about a malicious script making its way into an npm
package called eslint-scope
. After following the chat and reading the issue report on Github, they could tell that the attack exposed a vulnerability in the npm
package ecosystem, and that the purpose of it was to harvest the contents of .npmrc
files. They had a simple question:
why would someone steal npm credentials? what are they good for?
This coworker of mine is very smart, and if they didn't know the answer to this question, I thought it might be possible that other folks didn't know it, either.
A .npmrc
file exists to store configuration that will be read by npm
's own npm-config
tool. A lot of this configuration may be harmless and safe to check into version control, such as save-exact=true
or loglevel=warn
. But the .npmrc
is also the appropriate place to store the authentication tokens that may allow you to publish new versions of packages to which you have access.
Yesterday's attacker used a package maintainer's username and password combination, probably gleaned from an earlier attack, to give themselves new tokens. They used these tokens to publish malicious versions of the eslint-scope
and eslint-config-eslint
packages that attempted to run a pastebin
script harvesting further .npmrc
files. Thus, npm
credentials were both the vector and the target of this attack.
The attacker's goal in this case was certainly to harvest as many npm
credentials as possible, opening up even more projects to malicious code. With publishing rights to a variety of packages, they could have published versions of packages that, for instance, sent the contents of every input
tag on a web page to an unknown server. This is a scary enough prospect at companies like BuzzFeed, where I work. It's downright terrifying to imagine an attack like this succeeding against a company whose websites have access to confidential data.
In this case, the attack was not executed to a high level of competence. It was discovered when the following code failed:
r.on("data", c => {
eval(c);
});
As many people have pointed out, there is no guarantee that the request above will be completed, leading to a syntax error if it is not. Unfortunately, we can't always count on individuals with bad intentions to make convenient technical mistakes.
This is not the first time that malicious or misleading code has made its way into the npm
package ecosystem. It will not be the last. My personal view is that these attacks represent an inevitable consequence of JavaScript's global reach and the popularity of projects. Without speaking to the solutions proposed this time, as users of and maintainers of open software, we should be prepared for things to go wrong, and ready and willing to fix them when they do.
And when we discuss vulnerabilities in code amongst ourselves, I believe that it's important for our communication to be clear and accessible. A world of secure software depends on everyone knowing how it's safest to behave, and why.
Also, turn your two-factor authentication on!
I really recommend that anyone interested in these issues read the original report and the eslint
team's postmortem here.
I'd like to thank the eslint
and npm
teams for the quick action they took to solve these problems. Open source maintenance can be a thankless task, but never more so than on a day like yesterday. The wonderful Henry Zhu put up a gist that really helped me understand what happened. I recommend you read it here.