Skip to content

Commit 0f02b33

Browse files
authored
Merge pull request #2108 from HackTricks-wiki/update_Node_js_Trust_Falls__Dangerous_Module_Resolution_o_20260408_192638
Node.js Trust Falls Dangerous Module Resolution on Windows
2 parents 91bc4dc + ca08f80 commit 0f02b33

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

  • src/windows-hardening/windows-local-privilege-escalation

src/windows-hardening/windows-local-privilege-escalation/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,72 @@ For more information about how to abuse this check:
887887
dll-hijacking/writable-sys-path-dll-hijacking-privesc.md
888888
{{#endref}}
889889
890+
## Node.js / Electron module resolution hijacking via `C:\node_modules`
891+
892+
This is a **Windows uncontrolled search path** variant that affects **Node.js** and **Electron** applications when they perform a bare import such as `require("foo")` and the expected module is **missing**.
893+
894+
Node resolves packages by walking up the directory tree and checking `node_modules` folders on each parent. On Windows, that walk can reach the drive root, so an application launched from `C:\Users\Administrator\project\app.js` may end up probing:
895+
896+
1. `C:\Users\Administrator\project\node_modules\foo`
897+
2. `C:\Users\Administrator\node_modules\foo`
898+
3. `C:\Users\node_modules\foo`
899+
4. `C:\node_modules\foo`
900+
901+
If a **low-privileged user** can create `C:\node_modules`, they can plant a malicious `foo.js` (or package folder) and wait for a **higher-privileged Node/Electron process** to resolve the missing dependency. The payload executes in the security context of the victim process, so this becomes **LPE** whenever the target runs as an administrator, from an elevated scheduled task/service wrapper, or from an auto-started privileged desktop app.
902+
903+
This is especially common when:
904+
905+
- a dependency is declared in `optionalDependencies`
906+
- a third-party library wraps `require("foo")` in `try/catch` and continues on failure
907+
- a package was removed from production builds, omitted during packaging, or failed to install
908+
- the vulnerable `require()` lives deep inside the dependency tree instead of in the main application code
909+
910+
### Hunting vulnerable targets
911+
912+
Use **Procmon** to prove the resolution path:
913+
914+
- Filter by `Process Name` = target executable (`node.exe`, the Electron app EXE, or the wrapper process)
915+
- Filter by `Path` `contains` `node_modules`
916+
- Focus on `NAME NOT FOUND` and the final successful open under `C:\node_modules`
917+
918+
Useful code-review patterns in unpacked `.asar` files or application sources:
919+
920+
```bash
921+
rg -n 'require\\("[^./]' .
922+
rg -n "require\\('[^./]" .
923+
rg -n 'optionalDependencies' .
924+
rg -n 'try[[:space:]]*\\{[[:space:][:print:]]*require\\(' .
925+
```
926+
927+
### Exploitation
928+
929+
1. Identify the **missing package name** from Procmon or source review.
930+
2. Create the root lookup directory if it does not already exist:
931+
932+
```powershell
933+
mkdir C:\node_modules
934+
```
935+
936+
3. Drop a module with the exact expected name:
937+
938+
```javascript
939+
// C:\node_modules\foo.js
940+
require("child_process").exec("calc.exe")
941+
module.exports = {}
942+
```
943+
944+
4. Trigger the victim application. If the application attempts `require("foo")` and the legitimate module is absent, Node may load `C:\node_modules\foo.js`.
945+
946+
Real-world examples of missing optional modules that fit this pattern include `bluebird` and `utf-8-validate`, but the **technique** is the reusable part: find any **missing bare import** that a privileged Windows Node/Electron process will resolve.
947+
948+
### Detection and hardening ideas
949+
950+
- Alert when a user creates `C:\node_modules` or writes new `.js` files/packages there.
951+
- Hunt for high-integrity processes reading from `C:\node_modules\*`.
952+
- Package all runtime dependencies in production and audit `optionalDependencies` usage.
953+
- Review third-party code for silent `try { require("...") } catch {}` patterns.
954+
- Disable optional probes when the library supports it (for example, some `ws` deployments can avoid the legacy `utf-8-validate` probe with `WS_NO_UTF_8_VALIDATE=1`).
955+
890956
## Network
891957
892958
### Shares
@@ -2022,5 +2088,9 @@ C:\Windows\microsoft.net\framework\v4.0.30319\MSBuild.exe -version #Compile the
20222088
- [A Link to the Past. Abusing Symbolic Links on Windows](https://infocon.org/cons/SyScan/SyScan%202015%20Singapore/SyScan%202015%20Singapore%20presentations/SyScan15%20James%20Forshaw%20-%20A%20Link%20to%20the%20Past.pdf)
20232089
- [RIP RegPwn – MDSec](https://www.mdsec.co.uk/2026/03/rip-regpwn/)
20242090
- [RegPwn BOF (Cobalt Strike BOF port)](https://github.com/Flangvik/RegPwnBOF)
2091+
- [ZDI - Node.js Trust Falls: Dangerous Module Resolution on Windows](https://www.thezdi.com/blog/2026/4/8/nodejs-trust-falls-dangerous-module-resolution-on-windows)
2092+
- [Node.js modules: loading from `node_modules` folders](https://nodejs.org/api/modules.html#loading-from-node_modules-folders)
2093+
- [npm package.json: `optionalDependencies`](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#optionaldependencies)
2094+
- [Process Monitor (Procmon)](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon)
20252095
20262096
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)