Lockfile Poisoning: An Attack Vector to Introduce Malware in Software Supply Chain
Do you review changes in auto-generated lock files such as package-lock.json? If not, attackers might be exploiting the cognitive load of reviewing auto-generated changes to introduce malware in code
Lockfile Poisoning is an attack technique using which a developer with malicious intent can introduce malware in a project’s source code in a way which is almost invisible to code reviewers. This is an especially challenging and risky problem for open source projects where a contributor can sneak in malware by exploiting the cognitive load involved in reviewing changes in auto-generated lockfiles such as package-lock.json which many a times are hidden by default by Github and Gitlab. For example, this PR introduces a backdoor without any visible malicious code.
What is a Lockfile?
The software supply chain is complex. When you add a 3rd party library in your application, such as the popular axios library for the npm ecosystem, you are actually introducing 9 other libraries as dependencies in your application. These dependencies are resolved by the package manager e.g. npm which need to do some work such as resolving a library dependency with a semantic versioning constraint ~1.2.0 into an actual version say 1.2.5 by querying the package registry. Subsequently it needs to recursively resolve the dependencies of each dependency, forming a graph, till all dependency constraints are met. The graph below approximately represents how axios is resolved into its dependencies including transitive dependencies.
To avoid doing this work every time when `npm install` is run and to achieve reproducible builds, package managers usually lock this information into files so that it can just lookup what version of dependencies need to be used without re-calculating everything. This is the lockfile.
Targeting npm Ecosystem
The npm cli uses `package-lock.json` to store its locked dependency resolution graph, commonly known as the `lockfile`. However, unlike other package managers, `npm` choose to store the resolved URL of a package artefact in the `package-lock.json` itself. For example
"node_modules/@angular-devkit/core": {
"version": "16.2.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.3.tgz",
"integrity": "sha512-oZLdg2XTx7likYAXRj1CU0XmrsCfe5f2grj3iwuI3OB1LXwwpdbHBztruj03y3yHES+TnO+dIbkvRnvMXs7uAA=="
}
The source URL of the artefact and its integrity information is supplied through the same channel i.e. `package-lock.json`. This provides an interesting opportunity to introduce malware in source code through a PR due to
1. Cognitive load involved in reviewing auto-generated files e.g. `package-lock.json`
2. Github and Gitlab’s UX feature of hiding changes in auto-generated file by default
Attack Demo for npm Ecosystem
Detection
In order to have fairly reliable detection, let us first enumerate the abuse cases
Attacker can tamper artefact URL and integrity checksum
Attacker can introduce a new entry to achieve code execution during installation
[1] is a critical attack because it will impact the production environment since the application will be shipped with attacker controlled library thereby granting code injection primitive. [2] is more of a CI/CD impact, which may be critical as well due to sensitive information available in the environment. To detect this attack, we need to be able to
Identify if artefact sources are trusted
Identify if an additional entry is added
Using `vet` to Prevent Lockfile Poisoning
We recently implemented a framework-ish support for detecting lockfile poisoning across ecosystems that may be affected. It currently supports the npm ecosystem but we hope to research and support other applicable ecosystems as well.
The detection process involves
Verify the package source URL is from a trusted registry while supporting different URL schemes and conventions as per npm specification
Verify that the URL path is consistent with the `node_modules` path to detect attacks where a malicious package is published in public npm package registry
Try out vet
https://github.com/safedep/vet
Reference
https://blog.ulisesgascon.com/lockfile-posioned
https://snyk.io/blog/why-npm-lockfiles-can-be-a-security-blindspot-for-injecting-malicious-modules/
https://github.com/lirantal/lockfile-lint