You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-web/dependency-confusion.md
+103Lines changed: 103 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,6 +28,62 @@ If your project references a library that isn’t available in the private regis
28
28
29
29
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.
30
30
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
+
31
87
32
88
## AWS Fix
33
89
@@ -113,6 +169,7 @@ Operational tips:
113
169
- Only publish internal packages within the `@company` scope.
114
170
- For third-party packages, allow public registry via your private proxy/mirror, not directly from clients.
115
171
- 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.
116
173
117
174
### Python (pip / Poetry)
118
175
@@ -276,13 +333,59 @@ bundle config set disable_multisource true
276
333
- Name reservation: pre-register your internal names/namespaces in public registries where supported.
277
334
- Package provenance / attestations: when publishing public packages, enable provenance/attestations to make tampering more detectable downstream.
278
335
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.
0 commit comments