New npm Flaws Let Attackers Better Target Packages for Account Takeover
For the past few years, cybercriminals have been hijacking popular npm packages by taking over maintainers’ accounts. As part of our research at Team Nautilus, we discovered two flaws in the npm platform related to two-factor authentication (2FA). An attacker can use these flaws to target npm packages for account takeover attacks. We reported these findings to the npm team (GitHub), which quickly fixed the underlying security gaps.
However, our analysis shows that 32% of the top 35 npm packages are still at risk of account takeover from their dependencies’ owners. This can enable attackers to poison the root package or other npm packages that depend on those popular packages and, as a result, affect millions of npm users. In this blog, we’ll explore the details of the research and examine the security risks of direct and transitive dependencies on npm packages.
2021 Stack Overflow Insights
These days, developers use a lot of third-party open source packages throughout the software development process, which puts them at risk of software supply chain attacks. Popular package managers such as npm and their users are frequently the targets. Typically, adversaries will embed malicious code directly into a benign package or into one of its direct or transitive dependencies.
A notorious example is UA-Parser-JS, an npm package with millions of weekly downloads, which was quickly updated after being compromised with crypto mining and password-exfiltration malware.
How big a problem is this? A recent report shows that software supply chain attacks have increased by 650% in 2021 on top of year-over-year growth of 430% in 2020, where attackers injected malicious code into benign packages
If you’re interested to learn more about the various abuse methods and weaknesses of npm, it’s worth reading the excellent article “What are Weak Links in the npm Supply Chain?”
An attacker can gain access to the desired package in several ways. One method is to obtain one of the package maintainers’ credentials.
According to a study published in 2017, a security researcher was able to gain direct access to 14% of all npm packages (or indirect access to 54% of packages). He used brute force attacks or reused passwords discovered in other breaches, causing mass password resets across npm users.
Due to the structure of npm, hijacked or malicious packages have a greater impact. npm encourages creating small packages that solve a single problem. This leads to a multitude of smaller packages that each rely on several other packages.
As a result of the credential compromise research mentioned above, the researcher was able to access some of the most widely used packages, granting him access to a much broader range of valuable packages than he would have otherwise been able to access.
For example, here’s a dependency graph for express, one of the top-10 popular packages on npm, which has a weekly average of 25M downloads as of April 2022.
Attackers who gain access to express’ direct or transitive dependencies are likely to compromise the whole package.
Reality is more complicated because there are several aspects to consider that may affect the root package by one of its dependencies: pinning dependencies, package-lock.json, inability to overwrite a published package, and more. Matthew Bryant reviews several of them in “Zero-Days” Without Incident - Compromising Angular via Expired npm Publisher Email Domains.
A study from 2019 found that on average packages implicitly trust 79 third-party packages and 39 maintainers. Additionally, popular packages often influence more than 100,000 other packages, which makes them a prime target for attacks.
Security-focused improvements of npm
The security of npm has improved over time as the community became more aware of its security risks.
From February 2022, npm requires 2FA for all maintainers of top-100 npm packages by their dependencies.
Two-factor authentication is also planned for high-impact packages, which are any packages with more than 1 million weekly downloads or 500 dependencies.
For maintainers, 2FA is important because it immediately reduces the risks associated with compromised passwords. If a password is leaked, guessed, or even phished, it’s no longer enough to give an intruder access. Without confirmation of the second factor, a password is useless.
Furthermore, npm launched enhanced login verification – it will require additional verification to allow you to log in. A one-time password will be sent to the email address associated with your account if you don’t have 2FA enabled.
But setting up 2FA is always a stronger practice because a one-time password alone isn’t sufficient. If your password for npm was leaked, it’s likely that the password for your private email account has been compromised as well. In many cases, it will be the same password, which allows an attacker to bypass the one-time password sent to your email.
These improvements are all good news from the security perspective. Maintainers without 2FA for package hosting, releasing a new version, or signing in to their npm account are often weak links in the software supply chain.
However, as the saying goes, “A chain is only as strong as its weakest link”. It’s also necessary to enforce the same requirements for the maintainers of direct and transitive package dependencies to ensure package security.
npm information disclosure: 2FA enumeration
In our study, we tried to find out how many of the popular npm packages, whose maintainers were forced to enable 2FA, were still at risk of account takeover because the maintainers of their direct and transitive dependencies weren’t using 2FA.
Our first challenge was to determine whether users of npm had enabled or disabled 2FA. After all, information like this shouldn't be available to every user.
It turns out that there’s a simple way to discover it. As a creator of a package, any npm user can be added as a maintainer.
As a result, we can:
- Create and publish an npm package
- Browse to package settings page on - https://www.npmjs.com/package/<packlage_name>/access
- Add the npm user you’re looking to enumerate for 2FA as a maintainer. As an example, before attempting to log in to their account with the leaked password
The user 2FA status will be displayed in the maintainer window:
This feature is meant to protect developers and show how secure the user is when adding the user as a maintainer of a package. But it can also be abused.
In February 2022, we reported these issues to the npm staff via the bug bounty program of GitHub on HackerOne and got this answer:
“It’s a previously identified issue and is being tracked internally. We are actively working on remediating".
In March 2022, the fix was rolled out, and npm no longer shows a maintainer’s 2FA status.
npm information disclosure: ‘enforced tfa’ enumeration
As part of the study, we found a way to determine if a particular maintainer must enable 2FA and whether an organization requires its employees to use 2FA.
Any unauthenticated user can inquire if ‘enforced_tfa’ is enabled on another npm user or organization as well as determine if the user is a staff member and other details.
We were surprised to find out that every user or organization profile on npm included this information!
Here are the steps you can reproduce:
- Make a request to https://www.npmjs.com/~<username> or https://www.npmjs.com/org/<scope_name>
- View the responsefrom the request.
- Extract the value of ‘enforced_tfa’ and ‘isStaff’ in window.context json.
For example, we can see that Facebook (fb user) requires by npm to enable two-factor authentication.
We reported the following issues to the npm staff via the bug bounty program of GitHub on HackerOne. The issue was validated and fixed.
Timeline of the information disclosure for ‘enforced tfa’ enumeration
- 14-02-2022: The issue was reported to GitHub’s bug bounty program at HackerOne.
- 14-02-2022: Initial response received from GitHub that they were looking into the issue.
- 17-02-2022: Issue confirmed by the GitHub security team.
- 25-03-2022: Issue patched on npmjs.com.
npm research: Technical details
Now that we have a way to determine whether a user has 2FA enabled, we can calculate the percentage of the top npm packages at risk of account takeover from the maintainers of the packages’ dependencies (in case they haven’t enabled 2FA).
A user will be considered at risk when both of the following conditions are met:
- 2FA isn’t enforced
- An old/current password has been leaked
To determine this, we’ll use the haveibeenpwned API and the ‘enforced_tfa’ method to enumerate the enforced 2FA status of npm users.
We’ll write a short Python script that will summarize the results. To simplify the process, we’ll run the script on top-35 npm packages as of February 2022 (displayed on npmjs.com), and then we’ll check the enforced 2FA status of dependency maintainers and a password leak status.
It’s worth highlighting that if a false value is returned from the enforced_2fa variable, it doesn’t necessarily mean that the user hasn’t enabled 2FA. However, in this study, we will treat this user as a maintainer that hasn’t enabled 2FA yet so that we can assess the potential risk.
It’s also important to remember that if the package depends on a particular version of another package, attackers won’t be able to affect its dependencies. But in many cases, this may lead to other problems, such as vulnerable packages that are left unpatched.
Also, the package typically depends on its version using the ~/^ symbols, which indicate patches and minor releases. This gives an attacker the opportunity to push malicious packages with patches or minor releases that would directly affect the dependent package.
In addition, even if a package requires a specific version of another package, an attacker with control over one of the maintainers’ accounts can mark the package as vulnerable to push downstream packages to update to the attacker’s malicious package version.
Matthew Bryant provides more details about the impact of this in the article “Zero-Days” Without Incident - Compromising Angular via Expired npm Publisher Email Domains”.
To simplify the study, we marked a package at risk if at least one route allows dependencies to update the root package with malicious code. During the study, we found that there’s more than one way to do this in most cases.
The following data was obtained for the top-35 popular npm packages after executing the code above. The results refer to February 2022, and you can find the detailed statistics in the appendix.
npm research: A statistical analysis
Based on our analysis, 32% of top-35 npm packages are still at risk of account takeover from their dependencies’ owners, which allows attackers to abuse the root package.
It’s also important to note that when it comes to devDependencies, the outcome can be serious.
Practically, the results for devDependencies indicate a 72% exposure rate. Which means the maintainers of these devDependencies haven’t enabled two-factor authentication, and the root package doesn’t use a specific version of these dependencies (i.e., the root package uses the ~/^ symbols).
Furthermore, sometimes packages required in devDependencies are relatively negligible. The download average of devDependencies isn’t high (less than one million downloads) compared to production dependencies, which won’t require their maintainers to enable 2FA by npm.
In the following attack vectors, an attacker can gain direct access to the workspace of a root package’s maintainer.
Also, the results imply that whenever there are more indirect maintainers than the direct maintainers of a specific popular package, the package is at risk. There are cases where this ratio is dozens of times and even 125 times in the case of the body-parser package.
A package with many maintainers presents many opportunities to perform account takeovers and social engineering attacks. This is also true for direct and indirect dependencies as well as devDependencies packages.
ms package: The broader the community, the bigger the risk
Now let’s look more closely at the ms package. This package converts various time formats to milliseconds and is relatively small. As of April 2022, this package has 164m downloads a week with about 3200 dependent packages.
The main issue with this package is a large number of maintainers — almost 130.
If a package doesn't depend on a specific version of ms, it might be at serious risk. Like in the case of the humanize-ms package (6M weekly downloads), which depends on ms ^2.0.0 version.
Attackers could hide their identity by compromising one of the maintainers’ profiles to take advantage of an oversight by a large team. You can argue that many maintainers also give more supervision, and this is the nature of open source. This is true, but there must also be a limit.
In case you were wondering, about 66% of the ms package maintainers haven’t yet enforced 2FA, and variations of their passwords had been previously leaked.
Another way to avoid the risk of indirect maintainers’ account takeover is by “pinning” dependencies. For example, 78% of indirect maintainers of the debug package are at risk. However, there’s no way to affect the root package (debug package) because it uses the absolute version of dependencies in the package.json file.
The risks described above will be remediated once npm enforces 2FA for all maintainers of packages with more than 1 million downloads.
Taking it to the extreme: “no-one-left-behind” package
Have you ever heard of the “no-one-left-behind” package? It depends on almost 1000 npm packages from 2018. Imagine how many dependencies and indirect owners this package has and in how many ways it can be affected. Of course, this is an extreme case of dependencies, but it illustrates the point well.
The dependency graph for the “no-one-left-behind” package
In recent years, open source projects and npm in particular have improved their security. However, attackers are also evolving their methods and tools.
As opposed to defenders, attackers only need a single success (one compromised package in our case) to launch an attack kill chain.
According to our study, npm packages with a large number of maintainers are at higher risk of abuse. Developers need to take this into account and at least pin their dependencies while using them.
It’s up to us as developers to minimize the attack surface and make account takeover more challenging for attackers. Otherwise, the results can be damaging to the whole community.
Finally, we strongly encourage developers who contribute to open source and create packages to enable 2FA on all their accounts to keep their communities safe.
We’d like to thank the GitHub/npm security team for their quick response and professional remediation process.
How Argon can help
- Argon solution can identify a variety of anomalies in open source packages in use and detect suspicious behavior such as described in this blog.
- Argon provides visibility into the software supply chain and notifies teams of any deviations from the organization’s 2FA policy.
- Argon can alert teams not only to vulnerabilities in their code but also to misconfigurations and poor security policies in the organization.