Intro

  • We’ll be looking into how you can protect your organisation from recently discussed supply chain attack dependency confusion by alex birsan Read Here.
  • Also Check CVE-2021-24105

Background

To better understand the whole issue you first need to understand how package managers and registries like npm, pip, etc work and what they are used for. In this article, we will stick to npm to present a real-world case so that you can understand everything properly and use the same for practically implementing a fix.

npm install package-name-here

So whenever you type the above command in your terminal, npm as a package manager tries to fetch and install the requested package so that it can be used in your project.

npm fetches the package from a server known as npm registry, which can be a central public server like https://registry.npmjs.org/ or your own internally hosted registry server. During the installation, it reads a file called package.json from the requested package and follows the instructions given in package.json to install any sub dependencies or run any setup scripts as required.

A common setup in many organizations is to use an internal npm registry server for hosting the private packages that are to be shared within the entire org. This server is set up in such a way that if any package is not found internally then the server will just act as a proxy to central public servers like https://registry.npmjs.org/ and will fetch that package from the public server.

You can configure/change the registry server used by npm by below command

npm config set registry https://registry.npmjs.org/

Now let us see what can go wrong here, as already discussed in many articles over the internet, a feature called life cycle scripts supported by npm introduces a risk of malicious code execution. Here is an example of how it looks like.

package.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "name": "package-name",
  "version": "1.5.1",
  "description": "package-description",
  "main": "index.js",
  "scripts": {
    "preinstall": "any os command here"
  },
  "author": "asif",
  "license": "ISC"
}

So if any person installs a package that has the above package.json instructions embedded then any os level commands could be executed without any prior approval or even notice, this introduces a big risk like if any developer in your org makes a small typo while installing a package (like reaact instead of react). This is known as Typosquatting.

This could possibly lead to a complete compromise of a system, in case if anyone has published a malicious package with the name reaact to npm public registry.

This has happened many times in the past (Malicious npm package found) and security teams in many organizations including ours are using the concept of package-whitelisting to prevent such malicious packages from getting introduced into org systems/servers. I will be writing a separate article about how to practically achieve this whitelisting without compromising on development speed or convenience.


Depedency confusion

So now since you understand a bit of background about this issue, we can discuss the latest supply chain attack based on the same root cause i.e life cycle scripts. As already discussed above a common setup in many organizations is to make their internal npm registry act as a proxy to public ones if the requested package is not found locally on the server.

But what if we have a package named PrivatePackageA published in our internal npm registry and at the same time there is another package with the same name PrivatePackageA published in the central public npm registry.

If you run the below command, from where will be the package fetched?

npm install [email protected]

Depending on the configuration in most cases, the internal npm registry server will first check if PrivatePackageA version 1.2 is available locally if not then it will just fetch it from the configured central public server like https://registry.npmjs.org/.

This behavior is what actually introduces this new concept of dependency confusion attack, Here is how

  • Let us assume that you have a private package PrivatePackageA version 1.0 published to the internal org registry server at https://registry.yourcomapny.org/.

  • All your machines/servers will be getting this package via npm install if it’s being used by any project.

  • At the same time, an attacker has published a malicious package with the same name PrivatePackageA version 2.0 on the central public server like https://registry.npmjs.org/.

  • In this scenario, if the package is being used in any project or if simply the command given below is run in any of the systems/servers in your org
    npm install PrivatePackageA

  • then even though the developer/person executing this command expects that PrivatePackageA will be fetched from the companies internal registry server which is supposed to be safe but what actually happens is

  • npm fetches PrivatePackageA from the central public server like https://registry.npmjs.org/ since the latest version for the same package name was present there not on the internal registry server.

Hence in this way, a malicious package gets introduced in an org without any typo or any mistake from the person running npm install (e.g a developer, etc).


Mitigation Methods

As we know while fixing any security issue it’s equally important to strike a balance between security and convenience, hence we have discussed multiple mitigation methods so that you can decide for yourself which method suits best as per your organization’s needs.

Practical Mitigation Method 1

To present mitigation we will use a very well-known open-source private npm proxy registry verdaccio which is used by a lot of companies out there.

Steps

  • First and foremost enforce a scope to be used while naming any private packages within your org [npm scopes]. When used in package names, scopes are preceded by an @ symbol and followed by a slash, e.g. @somescope/somepackagename.
  • Now go to scoped packages section of your verdaccio config it will be something like below
1
2
3
4
5
packages:
  '@*/*':
    access: $all
    publish: $authenticated
    proxy: npmjs
  • You have to create another rule to remove proxy resolutions for private packages, this will make sure your private package names are never fetched from any sources other than your internal private registry.
1
2
3
4
5
packages:
  '@scope-name/*':
    access: $all 
    publish: $authenticated 
-   proxy: npmjs #remove this line
  • Make sure to replace scope-name above with your real scope and also access rules like access/publish as per your org policies.

Practical Mitigation Method 2

Steps

  • In this method before deciding on the scope name you need to make sure that you are the owner for that scope name in any central public server like https://registry.npmjs.org/, that is being used by your internal server for proxying. (it’s just like registering for a unique username at a website)
  • Then you have to enforce that scope to be used while naming any private packages within your org npm scopes. When used in package names, scopes are preceded by a @ symbol and followed by a slash, e.g. @somescope/somepackagename.
  • This works because scope names are unique on npm registries, once taken by a user they cannot be used by any unauthorized 3rd party/person for the publishing of packages. The concept is similar to package ids in the android play store.

Method 3 not so practical

  • Do not use any proxies for packages and fetch all packages from your internal repository only.
  • Keep adding new packages as and when required to your internal repository after a basic security check.
  • I know a big (3000 above employee) tech company doing this.
  • I would not recommend this approach since it adds a lot of friction to the CI/CD pipelines.

Final Thoughts …

I would strongly recommend using the package-whitelisting approach (will soon be writing a separate article on how to implement this in an org) along with Practical Mitigation Method 1 to reduce the security risk in your open-source supply chain.