Never assume a file downloaded from the Internet is safe. That warning also applies to NPM, the default package manager for Node.js. A vulnerability in package install scripts would let an attacker create a self-replicating worm that can spread through NPM packages.
"It is possible for a single malicious NPM package to spread itself across most of the NPM ecosystem very quickly," Sam Saccone, a software engineer at Google, wrote in his NPM hydra worm disclosure.
Like many other package managers, NPM supports lifecycle scripts, which can execute arbitrary commands on the system with the permissions of the current user. Though lifecycle scripts can be useful for cleaning up files after an installation, compiling binary dependencies, and automatically generating a configuration file, they can also be dangerous since the script can execute commands that modify the system.
"It is possible for a maliciously written NPM package, when installed, to execute a script that includes itself into a new package that it then publishes to the registry, and to other packages owned by that user," according to a post on the official NPM blog. However, the team said the benefits of installation scripts outweighed the risks of a potential worm attack.
The blog post downplayed the risks, noting the implications for the package scripts were "clear from the start," but not everyone was "fully aware" of them. Other than reiterating a handful of Saccone's recommended workarounds, the post did not provide guidance for users concerned that the packages they are installing may not be what they are expecting.
"NPM cannot guarantee that packages available on the registry are safe," the blog post said.
Worm spreads via package dependencies
The worm takes advantage of three NPM features: semantic versioning (semver), publishing to a centralized registry, and leaving users logged in by default. Because the user remains logged into NPM until they manually log out, any user who has logged in and is running an install, in effect, allows other modules to execute commands. Since install dependencies are not locked to a specific version, packages can push new versions, all with the ability to execute code. Finally, it is easy to ship packages to the central registry server to be installed by anyone.
The worm attack relies on social engineering to kick off the initial infection and the above-named features to continue spreading through the ecosystem.
First, the malicious author tricks an NPM module owner to install the infected package on to their system. This could be done by phishing or another malware attack. Once the package is installed, it creates a Trojanized version of the owner's NPM module and sets a lifecycle hook to execute the worm whenever the module is installed. The modified module is published to the owner's NPM account, at which point the worm modifies all other packages in that account to call the Trojan module as a dependency. The worm publishes new versions of each package in the account with a "bugfix," and the next time the modules are installed, the self-replicating code will be executed.
Sign up for CIO Asia eNewsletters.