找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4415

积分

0

好友

579

主题
发表于 4 小时前 | 查看: 5| 回复: 0

在自动化工作流领域,n8n 凭借灵活的节点设计和强大的扩展能力,赢得了不少开发者和企业的青睐。但也正因为它的数据流链路异常复杂,一旦某个环节轻信了用户输入,就可能演变成灾难性的安全漏洞。安全研究员 Simon Köck 近期披露了一个 n8n 高危漏洞(CVE-2026-33696,CVSS 评分高达 9.4),攻击者只需要构造一个看似普通的 HTTP 请求,就能在自托管甚至云端的 n8n 实例上实现完全远程代码执行(RCE)。

n8n 自动化工作流中原型链污染攻击链路图

这个漏洞最耐人寻味的地方在于,它完整展示了现代 JavaScript 应用中“原型链污染(Prototype Pollution)”如何一步步越过信任边界,并借助其他节点的 Gadget(跳板)完成联动,最终把一个看似无害的字段名变成系统的控制权。

从一行直觉的代码说起

漏洞排查的起点,往往是对常见代码模式的重新审视。在审计 n8n 内置节点实现时,研究员把目标锁定在那些“直接把用户输入的字符串当作普通对象键名”的地方。如果这类赋值没有对 __proto__constructorprototype 做防御性过滤,原型链污染的隐患就成立了。

在检查 nodes-base 库时,GSuiteAdmin 节点很快露出了马脚。这个节点带有一个 “Custom Fields” 功能,允许用户在创建或更新操作时自定义 Schema Name、字段名以及对应的值。由于这些参数直接来自工作流配置,任何拥有工作流编辑权限的攻击者都可以完全掌控它们。

问题核心代码非常简洁:

const customSchemas: IDataObject = {};
customFields.forEach((field) => {
    const { schemaName, fieldName, value } = field;
    customSchemas[schemaName] ??= {};
    (customSchemas[schemaName] as IDataObject)[fieldName] = value;
});

初看这段代码似乎没什么毛病:它按照 schemaName 对字段进行分组,再把键值对写入对应对象。但如果攻击者把 schemaName 传成 "__proto__",整个逻辑就会急转直下。

当执行 customSchemas["__proto__"] 时,JavaScript 的原型链机制被触发,返回的是全局的 Object.prototype。因为它不是空值,后面的 ??= 赋值被直接跳过;但紧随其后的这一行却真实执行了:

(Object.prototype)[fieldName] = value;

到这里就彻底失控了。攻击者成功将一组受控的属性和值直接写入了全局的 Object.prototype。换句话说,这个 Node.js 进程此后创建的所有普通对象,都会无条件继承这个被污染的属性。

巧妙的 Gadget 链:从污染走向 RCE

单纯的原型链污染,很多时候只会引发一些诡异的逻辑错乱。但如果搭配得当,它就能变成致命武器。研究员在这里找到了一条通往 RCE 的完整 Gadget 链,而关键的跳板正是 n8n 自带的 Git 节点。

整个攻击链路可以浓缩为:Webhook 触发 → GSuiteAdmin 节点污染原型 → Git 节点执行克隆 → RCE

当工作流调用 Git 节点时,底层依赖的 simple-git 库会创建一个干净的空环境对象 {} 来存放环境变量。由于这个对象继承自已遭污染的 Object.prototype,它便“意外”继承了攻击者预先注入的属性。

当 Node.js 的 spawn() 函数启动子进程执行 git clone 时,会遍历环境变量对象。此时,被污染的 GIT_SSH_COMMAND 属性就堂而皇之地混进了子进程环境。Git 客户端在处理 SSH 类型的远程仓库 URL 时,会严格遵循 GIT_SSH_COMMAND 环境变量,并将其作为终端命令执行。

攻击者构造请求时,只需发送这样的 Payload:

{
  "schemaName": "__proto__",
  "fieldName": "GIT_SSH_COMMAND",
  "value": "sh -c 'id; cat /etc/passwd' --"
}

即便前面的 GSuiteAdmin 节点因为调用 Google API 失败而报错,原型链污染的副作用也已经永久生效。随后执行的 Git 节点会直接触发携带恶意命令的 GIT_SSH_COMMAND,以当前 n8n 进程用户的身份,在宿主机上执行任意指令。这种突破边界的方式,对于熟悉安全渗透与漏洞分析的读者来说,应该并不陌生——用户输入一旦触碰动态语言的原型机制,防线往往比想象中更容易松动。

意料之外的破坏:静默的拒绝服务

除了直接的远程代码执行,这个漏洞在没有 RCE 链的情况下同样具备极强的破坏力。原型被污染后,n8n 底层依赖的 ORM 框架 TypeORM 在执行数据库查询时,内部的 buildWhere 函数会通过 for...in 循环遍历对象属性。

这些意外引入的污染属性,会被 TypeORM 误认为合法的数据库查询条件,从而在每次数据库交互时抛出 EntityPropertyNotFoundError 异常。结果就是:整个 n8n 的 Web 界面迅速陷入瘫痪,所有正在运行的工作流全部崩溃,实例只能通过完全重启来恢复。

修复与启示

这个漏洞的修复方案其实相当直观:在把任何外部输入的字符串用作对象键名之前,必须进行严格的黑名单校验(比如拦截 __proto__constructorprototype),或者直接采用安全的无原型对象 Object.create(null) 来存储动态数据。事实上,n8n 代码库中原本就存在带原型链防护的 deepMerge 工具函数,只是 GSuiteAdmin 节点在实现时遗漏了这一层安全边界。

对开发者而言,这起事件再次敲响警钟:在动态语言的复杂生态中,对用户输入的信任本身就应该被视为一种潜在的系统威胁。而对安全从业人员来说,漏洞挖掘的魅力正在于此——把看似微不足道的逻辑缺陷,通过精巧的链条组合,最终推演成直达系统内核的钥匙。如果你对这类开源项目的源码分析与实践感兴趣,云栈社区也有不少同路人持续追踪相似案例。

参考文献

https://simonkoeck.com/writeups/n8n-gsuiteadmin-prototype-pollution-rce




上一篇:STM32单片机C语言工厂模式实战:传感器驱动的解耦与注册机制
下一篇:LLM响应慢导致Agent链路超时?分层超时熔断策略详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-19 23:47 , Processed in 0.936129 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表