Threat Alert: Private npm Packages Disclosed via Timing Attacks

Threat Alert: Private npm Packages Disclosed via Timing Attacks

We at Aqua Nautilus have discovered that npm’s API allows threat actors to execute a timing attack that can detect whether private packages exist on the package manager. By creating a list of possible package names, threat actors can detect organizations’ scoped private packages and then masquerade public packages, tricking employees and users into downloading them. This kind of attack is linked to a broader category of supply chain attacks. Over the past few years, we’ve seen an increase in the volume and variety of such attacks in the wild. In this blog we’ll dig deeper into this issue and demonstrate how you can mitigate the risks.

Timing Attack to Detect Private Packages on npm

Our research has shown that by using a timing attack a threat actor can detect the existence of private packages via npm’s API.

For instance, when an unauthenticated user is sending to the npm’s API a GET request (<scope_name>/<secret_package_name>) to receive information about a private (scoped) package, the response is that this package isn’t found (http 404 response), whether the package ever existed or not. In the screenshot below, you can see how we sent a request and received a “404 Not found” response (marked in red).

Request returned 404-Not found as the request came from an unauthenticated and unauthorized userOur example shows how we can request API information about secret-packages,
which is a private package under the random-organization scope using Postman.
This request returned 404-Not found as the request came from an unauthenticated and unauthorized user. Additionally, we can see that the server responded after 686 milliseconds.

If a threat actor sends around five consecutive requests for information about a private package then analyzes the time taken for npm to reply, it is possible for them to determine whether the private package in fact exists. More accurately, this would show whether the package exists now or if it had existed in the past though is now deleted. In both cases, it would be the same result.

Due to this, we can assume that this flaw is embedded in the architecture of the API and is a result of the caching mechanism. To validate that this flaw exists, we conducted the following steps:

Creating a private package

As seen in the screenshot below, we created a private npm package and uploaded it.

we created a private npm package and uploaded it

We then used the organization random-organization to upload the npm package secret-package. An authenticated user should easily be able to view this package including its name, while an unauthenticated user shouldn’t get any disclosure information about this package. As you can see below, we verified the existence of this package with an authenticated user that belongs to random-organization via browser.

Verification of the existence package with authenticated user belonging to “random-organization” via browser

Executing a timing attack

We compared the time it takes to search for a private package that exists with a private package that doesn’t exist. For that, we generated a single consecutive request. But we didn’t find any significant differences.

From various systems, we started generating requests to receive private packages that did exist then compared the results with requests for private packages that did not. In doing so we found a noticeable difference!

Next, we collected and analyzed our findings to optimize the timing attack. We found that if we generated approximately five consecutive API requests as an unauthenticated user and looked for our new private package, it takes on average 648 milliseconds. Yet, if we generated about five consecutive API requests as an unauthenticated user to look for a private package that didn’t exist, it takes on average 101 milliseconds. Consider that if you try to replicate our exact results, there may be some differences due to connection strength and network speed. Still, the results should be quite similar.Response time in microseconds

Response time for private versus public packagesAs you can see in the graph and table above, it takes on average less time to get a reply for a private package that does not exist compared to a private package that does.

Supply Chain Attack via Code Packages

Threat actors often seek various ways to penetrate your organization. Over the past few years, we’ve seen a dramatic increase by hundreds of percentage points in supply chain attacks.

In some cases, the threat actors’ goal is to gain access to open-source packages/projects and poison them.

Other times they masquerade as private or public packages/projects, deliberately misspelling their names in order to trick unsuspecting victims into downloading their malicious package instead of legitimate popular ones (i.e., installing the Python package 'Padnas' instead of 'Pandas').

When this occurs, it’s not surprising that these incidents get wide coverage in the media. For instance, Bleeping Computer recently published a story about a supply chain attack in npm that impacted hundreds of websites and apps. In another report, they explained the risks of private package names exposure on npm.

How attackers can merge everything to an attack

A Scoped Confusion attack usually starts with a threat actor who collects intelligence about a specific organization:

A possible package names list

With this in mind, we thought about a few methods that could be used to create a possible package names list:

  • Guess the names of the private packages used by a specific organization by performing a dictionary or a guessing attack.

    Attackers may try to improve the dictionary list of specific organizations' private packages by looking for patterns or combinations in the organizations' public packages. For example, a contso organization might have public packages that begin with @contso/contso -*, @contso/cnt-*, @contso /core-*.

    Prefixes like these can be used by an attacker to tweak his list.

  • Online public datasets (such as store historic information about packages. An attacker could search for public packages that were deleted since they may have been converted to private packages.

  • It’s possible for an attacker to map all the scoped packages on npm that don't have public packages, then create phony malicious packages with the same name. Additionally, attackers can use the npm API to map packages by average download per week to identify the most widely used packages. For example, a package called @graphql-codegen/visitor-plugin-common receives 2.2M downloads per week. However, there in fact is no public package called visitor-plugin-common on npm. Thus, the attacker can create such a package in order to deceive users into installing it. It’s important to note that npm blocks you from creating and publishing public packages with the names of popular scoped packages, but this is not always the case.

Running a timing attack

Now that the attacker has a potential list of scoped private packages, a timing attack could be generated. Threat actors might tweak the algorithm to make minor modifications in their package names list in order to increase the chance of discovering an existing package.

Once the timing attack has finished running, the threat actors would analyze the results, retaining the packages with higher average response times – meaning that the private packages do exist.

Building public packages

Now that the threat actors have created a list of possible private scoped packages, they need to check that there are no public packages (package without a scoped) on npm with the same names, meaning they can create malicious package under the public scope of npm.

Note that we don’t encourage cybercrime. We merely describe here ways in which threat actors build their supply chain attacks.

Summary & Mitigation

In this blog we’ve explained how we discovered a flaw in npm’s API which is disclosing information about organizations' private packages. Threat actors have the capability to create a list of potential private package names and run timing attacks to verify their existence. Later, threat actors could create public packages masquerading as legitimate private ones and trick unknowing developers into downloading malicious packages.

We have disclosed this information to GitHub which, in response, replied that this architecture of the API is by design.

"Architectural nuances prevent us from systematically preventing timing attacks from determining whether a specific package exists.”

Here are some steps you can take to mitigate these risks:

  1. Gather a list of all your organization’s private and public packages on all the package management platforms.
  2. Actively look for typo squatting, lookalikes, or masquerading packages. Verify that there are no other packages with the same name as your internal private packages.
  3. If you find any similar packages, make sure that they do not contain malware and notify the relevant stakeholders.
  4. If you don’t find public packages similar to your internal packages, consider creating public packages as placeholders to prevent such attacks.
  5. If you would like to learn more about protecting yourself when using npm, you can read the following npm blog “Avoiding npm substitution attacks”.

The timeline of the discovery:

  • 03-08-2022: The issue was reported to GitHub’s bug bounty program at HackerOne.
  • 03-25-2022: GitHub triaged and responded: “Because of these architectural limitations, we cannot prevent timing attacks from determining whether a specific private package exists on npm

Picture of Yakir Kadkoda

Yakir Kadkoda

Yakir is a Security Researcher at Aqua Nautilus, Aqua’s research team. He focuses on finding and researching new vulnerabilities and attack vectors in cloud native environments. Prior to Aqua, he worked as a red teamer. When he is not at work, he enjoys baking and cooking and is particularly interested in the science of cooking.

Security Threats, Supply Chain Attacks

Subscribe to Email Updates

Popular Posts

Filter by Topic

Show more...