On Nov. 20, 2018, it was discovered that EventStream, a highly popular JavaScript library, was compromised with the addition of a third-party dependency, flatmap-stream, containing encrypted malicious code. The attack targeted other Node.js libraries used in cryptocurrency wallets. Basically, if your application used both EventStream and a cryptocurrency-related library called copay-dash, then the malicious code from flatmap-stream would try to steal Bitcoins from your cryptocurrency wallets.
How was the malicious code inserted?
EventStream is a very popular library with almost 2 million downloads per week. Even so, the original owner, Dominic Tarr, has not maintained it since 2012. So, in September 2018, Tarr transferred project ownership to a volunteer to maintain it.
The new owner, a user called Right9ctrl, turned out to be a bad actor. Soon after taking ownership of EventStream, Right9ctrl added a dependency to flatmap-stream. Until then, flatmap-stream was a little-known library that had no downloads on NPM. Right after that, someone (it’s not clear who) made a change to flatmap-stream that included the malicious code. Now the EventStream library was pulling in a malicious dependency.
Three days later, Right9ctrl removed the dependency on flatmap-stream from the EventStream library (perhaps to hide his or her tracks). However, people who included the EventStream package in their projects during those three days pulled in the malicious code. And if they haven’t updated their projects since then, it may still be there.
What does the malicious code do?
The malicious code, which targets the copay-dash library, looks for cryptocurrency wallet profiles. It maps the wallet IDs with the public keys of profiles with balances over 100 BTC (Bitcoin) or 1,000 BCH (Bitcoin Cash). Then it sends the identified wallets to a server in Kuala Lumpur (Malaysia).
Then the code captures the passwords to these wallets and sends them to the same server. The malicious code can obtain these passwords by overwriting the Credentials.getKeys function in one of the Bitcoin-related libraries using the ability to redefine functions through JavaScript prototypes. Thus, the attackers can obtain both Bitcoin wallet IDs and passwords.
What makes this incident unusual?
What makes the EventStream incident so unusual is the attack approach. A common method of distributing malware is typosquatting, where an attacker publishes a malicious package with a similar name as a popular package. But in this case, the new owner/attacker gained ownership of the original package through a legitimate channel.
“This represents a scary social-engineering vector for malware,” Cory Doctorow said on the Boing Boing blog. “A malicious person volunteers to help maintain the project, makes some small, positive contributions, gets commit access to the project, and releases a malicious patch, infecting millions of users and apps.”
This represents a scary social-engineering vector for malware — Cory Doctorow
What’s not unusual is for owners of popular packages to give them away. As Tarr points out, “There are likely to be many other modules in your dependency trees that are now a burden to their authors” and thus may end up in the hands of new owners.
Has EventStream been fixed?
Interestingly enough, after someone alerted Tarr of the vulnerability in EventStream, he could not update the code on NPM. He had already transferred the rights to the library to Right9ctrl. Thus, he had to contact NPM to remove the malicious package from the repository.
NPM has now pulled flatmap-stream, so applications trying to download it will return an error. If your projects include any of these packages, update all dependencies to the latest recommended versions.
What can we learn?
So what does this story tell us? Dependency management is hard. NPM verifies the immediate dependencies that you add to your package when you install them (as long as you don’t ignore the notifications). But those dependencies may become compromised later if someone adds another dependency that has malicious code. Even if a library contains malicious code for only a few days, for a popular library, that’s enough to affect thousands or millions of users. If these users don’t update their applications, the malicious code will still be there. And the malicious dependency may not necessarily target the original application. Instead, it might go after peer dependencies used in the same or other applications.
How can I protect my code from malicious dependencies?
Take these steps to make sure your code is not using any malicious or outdated dependencies:
- Perform malicious code detection analysis that scans your code and dependencies for unexpected functionalities or other unusual code that may indicate a previously unknown intrusion into your codebase. Then you can lock your dependencies in the package.json file to download only specific versions and not automatically update to the latest ones. When a new version comes out, you can review it and allow the upgrade only if the dependency is safe. However, this process has major overhead and might be suitable only for highly sensitive projects.
- Keep a bill of materials (BoM), a list of components and dependencies in your codebase. Just knowing what your code depends on will help make you aware of the third-party risks that you might be exposed to. And there are many. Last year, just a month after GitHub began scanning open source libraries for vulnerabilities, they’d already found 4 million bugs in 500,000 projects.
- Use software composition analysis (SCA) tools such as Black Duck. You should run SCA continuously, not just once, because as this example shows, dependencies change all the time. A good SCA solution will alert you to newly discovered vulnerabilities in your BoM and give you information beyond that found in the NVD. (Synopsis on Nov.30, 2018 via McGallen and Bolden)