Skip to content

Commit b1150ce

Browse files
authored
Merge pull request #2083 from HackTricks-wiki/update_Frequently_Asked_Questions_About_the_Axios_npm_Sup_20260402_020436
Frequently Asked Questions About the Axios npm Supply Chain ...
2 parents 7643c45 + 4787cce commit b1150ce

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

src/pentesting-web/dependency-confusion.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,62 @@ If your project references a library that isn’t available in the private regis
2828

2929
Developers frequently leave versions unpinned or allow wide ranges. When a resolver is configured with both internal and public indexes, it may select the newest version regardless of source. For internal names like `requests-company`, if the internal index has `1.0.1` but an attacker publishes `1.0.2` to the public registry and your resolver considers both, the public package may win.
3030

31+
### Related pattern: compromise of a legitimate package release
32+
33+
Dependency confusion is not the only way to get install-time execution. If an attacker compromises the maintainer account or publishing token of a legitimate package, they can publish a malicious version of the real package and obtain code execution on every machine that installs it.
34+
35+
Common pattern in the npm ecosystem:
36+
- The attacker modifies only `package.json` and adds a new dependency.
37+
- The new dependency is **never imported** by the main library, so source review of the application code may look clean.
38+
- The dependency contains a `preinstall`/`install`/`postinstall` hook that runs automatically during `npm install`, `npm ci`, Yarn, pnpm, or CI builds.
39+
- The hook fetches or drops the real payload, often choosing per-OS implants for macOS, Windows, and Linux.
40+
41+
This is a useful red-team and incident-response mental model because **importing the victim library is not required**. The execution path is installation-time, not runtime.
42+
43+
Minimal malicious pattern:
44+
45+
`package.json` of the compromised package:
46+
```json
47+
{
48+
"name": "popular-lib",
49+
"version": "1.2.4",
50+
"dependencies": {
51+
"helper-lib": "^4.2.1"
52+
}
53+
}
54+
```
55+
56+
`package.json` of the injected dependency:
57+
```json
58+
{
59+
"name": "helper-lib",
60+
"version": "4.2.1",
61+
"scripts": {
62+
"postinstall": "node setup.js"
63+
}
64+
}
65+
```
66+
67+
Practical notes:
68+
- CI/CD runners are especially valuable targets because the hook runs before tests and often has access to cloud credentials, package tokens, signing keys, and deployment secrets.
69+
- If you are trying to prove impact in an authorized exercise, `postinstall` is usually enough to demonstrate install-time code execution without modifying application logic.
70+
- Defenders should remember that `npm ci` still runs lifecycle scripts unless `--ignore-scripts` is set.
71+
72+
### Obfuscated Node.js droppers and manifest laundering
73+
74+
Malicious install hooks often try to survive quick review by:
75+
- Hiding C2 strings or commands behind layered transforms such as reversed Base64, XOR, or split strings.
76+
- Dynamically loading Node modules (`fs`, `os`, `child_process`, `execSync`) only at runtime to reduce obvious static indicators.
77+
- Deleting the dropper after execution and restoring a benign-looking manifest.
78+
79+
One anti-forensic trick is **manifest laundering**:
80+
1. Run the malicious hook.
81+
2. Delete the malicious `setup.js` or equivalent.
82+
3. Delete the malicious `package.json`.
83+
4. Rename a benign stub such as `package.md` back to `package.json`.
84+
85+
After infection, the installed dependency directory may look clean unless investigators review install logs, lockfile changes, registry metadata, package tarballs, file timelines, or known-good hashes.
86+
3187

3288
## AWS Fix
3389

@@ -113,6 +169,7 @@ Operational tips:
113169
- Only publish internal packages within the `@company` scope.
114170
- For third-party packages, allow public registry via your private proxy/mirror, not directly from clients.
115171
- Consider enabling npm package provenance for public packages you publish to increase traceability (doesn’t by itself prevent confusion).
172+
- For high-risk environments, install with scripts disabled first (`npm ci --ignore-scripts`) and only allow scripts in controlled build stages.
116173

117174
### Python (pip / Poetry)
118175

@@ -276,13 +333,59 @@ bundle config set disable_multisource true
276333
- Name reservation: pre-register your internal names/namespaces in public registries where supported.
277334
- Package provenance / attestations: when publishing public packages, enable provenance/attestations to make tampering more detectable downstream.
278335

336+
### Detecting unauthorized publishes in trusted-publisher pipelines
337+
338+
If a package normally uses npm trusted publishing with GitHub Actions or GitLab OIDC, a release pushed with a stolen classic token often looks different from legitimate releases.
339+
340+
Useful heuristics:
341+
- The package version exists in the registry but lacks the expected trusted-publisher / provenance metadata.
342+
- There is no matching git tag or release commit for the published version.
343+
- The package tarball adds a dependency whose only purpose is a lifecycle hook.
344+
- The newly added dependency is never referenced by the main library source.
345+
- The lockfile or install logs show `preinstall` / `postinstall` execution shortly before network egress or secret access from a runner.
346+
347+
This is not limited to dependency confusion: it also catches compromise of maintainer credentials or leaked automation tokens.
348+
349+
### Cooldown / age-gate controls for fresh releases
350+
351+
Fresh malicious versions are often detected and removed quickly. Delaying adoption of newly published versions can block a large class of opportunistic supply-chain compromises:
352+
353+
```yaml
354+
# pnpm-workspace.yaml
355+
minimumReleaseAge: 10080 # 7 days in minutes
356+
```
357+
358+
```yaml
359+
# .yarnrc.yml
360+
npmMinimalAgeGate: "7d"
361+
```
362+
363+
```toml
364+
# bunfig.toml
365+
[install]
366+
minimumReleaseAge = 604800 # 7 days in seconds
367+
```
368+
369+
```ini
370+
# .npmrc
371+
min-release-age=7
372+
```
373+
374+
These controls do **not** replace lockfiles or trusted publishing, but they reduce exposure to packages published minutes or hours earlier.
375+
279376

280377
## References
281378

282379
- [https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610)
283380
- [https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d](https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d)
284381
- [https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping](https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping)
285382
- [https://yarnpkg.com/configuration/yarnrc/](https://yarnpkg.com/configuration/yarnrc/)
383+
- [https://www.tenable.com/blog/faq-about-the-axios-npm-supply-chain-attack-by-north-korea-nexus-threat-actor-unc1069](https://www.tenable.com/blog/faq-about-the-axios-npm-supply-chain-attack-by-north-korea-nexus-threat-actor-unc1069)
384+
- [https://docs.npmjs.com/trusted-publishers/](https://docs.npmjs.com/trusted-publishers/)
385+
- [https://docs.npmjs.com/generating-provenance-statements](https://docs.npmjs.com/generating-provenance-statements)
386+
- [https://docs.npmjs.com/cli/v11/using-npm/changelog/](https://docs.npmjs.com/cli/v11/using-npm/changelog/)
387+
- [https://pnpm.io/settings](https://pnpm.io/settings)
388+
- [https://bun.sh/docs/runtime/bunfig](https://bun.sh/docs/runtime/bunfig)
286389

287390

288391
{{#include ../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)