Nothing is safe these days, not even Node’s npm.The Node.js Package Manager (or just npm) allows the author of a malicious package to infect other packages and propagate malicious scripts across the npm ecosystem and in the builds of legitimate projects.
Three (+ one) issues are the cause of npm’s “vulnerability”
Before explaining what Mr. Saccone found, we also must mention that when an “npm install” command is executed, besides downloading and installing the desired package into your project, npm allows the newly downloaded package to load and run adjacent scripts (called lifecycle scripts).
These scripts are executed with the user’s current privileges, which on some operating systems may be root/system. This is a known issue, but something that Mr. Saccone wanted to raise a concern about, since other package managers also allow them.
What Mr. Saccone discovered is an exploit, which he described in this write-up (embedded below) as leveraging three npm features.
The first revolves around the fact that npm uses the SemVer versioning system. SemVer is not the problem here, but the fact that these dependencies aren’t locked to a specific version. Some developers load dependencies with version number ranges, and even if a developer locks down their code with fixed versions, some of the dependencies they load have other dependencies of their own, which may use version number ranges.
The second problem revolves around users who have an npm author account, meaning they can publish packages to the npm registry. npm doesn’t have an automatic logout mechanism, so someone who logged into the npm system remains logged in up until they decide to log out.
The third issue is the registry itself. npm is a centralized registry. Code published via npm doesn’t go through a review process and is available to anyone, almost instantly, as someone hit Enter and the script was uploaded online.
One rogue npm package could propagate across the whole registry
With all these issues, Mr. Saccone has described a very simple attack, which is a simple worm virus. It all starts with a rogue npm pacakge, which, besides legitimate features to entice other developers to use it, also includes malicious code.
When another developer finds this malicious package, and they deem the legitimate code to be useful in an idea they may be working on, they will include it in their project via an “npm install” command.
When this malicious package is downloaded and installed, those lifecycle scripts we mentioned earlier can execute malicious behavior on the infected machine using the user’s full privileges.
If the developer who installed the malicious package is also an npm author, they’ll probably still be logged in, so the malicious lifecycle script could very easily search the victim’s repositories, add its malicious code to their content, and then publish a new version (patch level – 0.0.x). Additionally, the malicious lifecycle script could also add the original malicious package as a dependency to the victim’s repos.
Since the updated modules are only at a bugfix level, other repositories that rely on the infected victim’s repos, if they haven’t locked down version numbers and use SemVer ranges, will automatically load the malicious updates inside their projects.
Using this simple scenario, which Mr. Saccone dubbed npm hydra worm, an attacker can wreak havoc on the entire npm registry in a matter of days.
npm doesn’t have the resources to scan and review everyone’s packages
Mr. Saccone has disclosed the issue to npm at the start of January, but the company has said they don’t have the resources to scan all code uploaded to their registry and that they’ll have to rely on the community to detect such packages when they are detected.
“Ultimately, if a large number of users make a concerted effort to publish malicious packages to npm, malicious packages will be available on npm. npm is largely a community of benevolent, helpful people, and so the overwhelming majority of software in the registry is safe and often useful,” the company wrote on its blog yesterday.
“We hope the npm community continues to help us to keep things that way, and we will do our best to continuously improve the reliability and security of the registry,” npm also adds.
Currently, as it stands, npm won’t take any steps to fix this issue, since it thinks the repository’s good parts far outweigh the dangers of this exploit scenario. The company is also recommending that developers install packages by limiting lifecycle scripts, or disabling them altogether, see code below:
### Install package without running scripts
npm install [pkgname] --ignore-scripts
### Disable scripts execution globally
npm config set ignore-scripts true
Furthermore, npm leadership is also currently exploring the scenario of using 2FA (two-factor authentication) for any npm publish operations, as a way to prevent such worms from spreading.
Recent left-pad debacle has already pointed out weaknesses in npm
The issue has been taken seriously enough that US-CERT (United States Computer Emergency Readiness Team) issued yesterday a public alert about this attack scenario.
In the past years, Node.js, as a technology, has spread to an immense number of companies in the US and worldwide, which may be affected if their local code repos are compromised.
Coincidentally, just three days ago, the recent left-pad debacle proved the dangers of rogue packages as well. When a copyright dispute angered developer Azer Koçulu enough to delete his entire repos, a malicious actor could have easily taken over his liberated namespaces and use them to publish malicious packages to other packages.
While nothing happened, this showed that npm is far from being perfect and that developers and npm should start thinking more about security as the service grows in popularity. If we’ve learned something from malware authors, it is that they’ll go wherever the users are, and more and more companies these days are using Node.js, so prepare to see more npm or Node.js-based attack vectors.