November 27, 2018

The very popular NPM package event-stream, which exposes a number of helpers for working with streams inside a node application, was altered to include malware that steals crypto coins. The attack targets the Copay Bitcoin Wallet application.

How did this happen?

The very popular event-stream package, with around 2 million downloads a week, hasn’t been in active development for a couple of years. The author of the package, dominictarr, maintains a large number of projects and this package did not get much attention lately.

Somewhere in the beginning of September, a user with GitHub handle right9ctrl offered to help out maintaining this package. The owner dominictarr agreed and gave right9ctrl access rights on both GitHub and NPM without knowing the bad intentions of this user.

When right9ctrl started contributing to the package, at first there were small commits for some updates and fixes. After that a new version followed (v3.3.4 => v3.3.5), nothing special. Then, on September 9th, a utility function was introduced using a new dependency flatmap-stream and the version was bumped to v3.3.6. This new dependency was removed a week later on September 16th, and flat map functionality was implemented directly in the package. Still nothing special to add a new dependency and a few days later decide to implement the code yourself.

But this was actually a cover up, because the published NPM version v3.3.6 still had the malicious flatmap-stream package included, while you could not detect that in the latest version of the source code on GitHub.

Semantic Versioning

Since the event-stream package has been on v3.3.4 for some time, this version will be included in a lot of applications and libraries already like this:

"dependencies": {
    "event-stream": "^3.3.4",
    ...
}

It means that if you bump the semver patch or minor version number, a new install of the package will look for the most recent version that falls in the specified version range. So when you do a npm install in a clean project folder or on a build server, you will get event-stream v3.3.6 instead of v3.3.4. Because that is what you normally want, fixes and improvements without breaking changes.

The Attack

So, with some minor changes to the very popular library event-stream, right9ctrl managed to inject the malicious flatmap-stream package into millions of other applications.

When you first look at the flatmap-stream package, there seems nothing wrong with it. It does implement a flat map for streams like it says. But the strange thing about it is, that it has only 1 contributor, almost no downloads on NPM and was created just before it was included into event-stream.

And the real problem is that the minified version of the source is not the actual source. The minified file has some additional code. Fallingsnow unminified the code and detected what is really happening there.

Hidden and encrypted

The additional minified code requires a ./test/data.js file, which contains an array of AES256-encrypted strings. It decrypts the contents of ./test/data.js using npm_package_description as the AES256 key, and attempts to execute the result. This npm_package_description is actually an environment variable set by NPM when installing the modules in the dependency tree of the main application.

So, since the package description is the key to decrypting the malicious code, there should be an application that the attacker is targeting. Listing all packages on NPM that use event-stream, and brute-force trying to decrypt the code reveals that the application with the description "A Secure Bitcoin Wallet" is the target. This is the open source Bitcoin Wallet application Copay.

What it does

When the developers / build server of Copay run the build script, the code generated is modified before being bundled into the application. The injection was designed to harvest account details and private keys from accounts having a balance of more than 100 Bitcoin or 1000 Bitcoin Cash.

For any other application, decrypting the malicious code will fail, an error is silently handled and nothing will happen.

Protect yourself

Although this attack targets Copay Bitcoin Wallet users, you really don't want this malicious code in any other application. These are the things you can do.

Copay users

Bitpay states "If you are using any version from 5.0.2 to 5.1.0, you should not run or open the Copay app" in issue #9346 in their source repo.

  • make sure not to use these affected Copay versions, and install security update v5.2.0
  • create a brand new wallet on the new version of Copay
  • transfer all funds from old wallets to new wallet using the Send Max feature

Developers

If you're using event-stream in your own application, make sure that you're not using the updated and malicious v3.3.6. First, clean your node_modules folder, then pin the package to exact version 3.3.4 like this:

"dependencies": {
    "event-stream": "3.3.4",
    ...
}

That is, the version number without the caret (^) or tilde (~) prefix. And, finally, you can restore packages again with npm install.

Finally

This attack was a very clever one. It's hard to find these kind of things slipping into your own code. But to reduce your exposure, make sure you stick to popular, well-maintained packages.

  • do you really need a package, or can you write it yourself in a few dozen of lines?
  • does it have high download numbers on NPM?
  • does the GitHub repo appear well-maintained and active?
  • are there multiple maintainers?
  • has the package been updated recently?

And always make sure you know what's running in you code base.