一个Schema名称如何变成n8n的RCE

admin 2026-08-21 05:29:25 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: n8n的GSuiteAdmin节点存在原型链污染漏洞,用户可控的schemaName字段被直接当作对象键使用,攻击者传入proto可污染全局Object.prototype,再配合Git节点的GITSSHCOMMAND环境变量实现远程代码执行。攻击路径为Webhook到GSuiteAdmin再到Git节点。修复方案是对schemaName进行proto、constructor、prototype等危险属性名的黑名单检查。 综合评分: 88 文章分类: 漏洞分析,红队,WEB安全,安全工具,安全开发


一个 Schema 名称如何变成 n8n 的 RCE

幻泉之洲

2026年8月19日 14:59 北京

在小说阅读器读本章

去阅读

n8n 的 GSuiteAdmin 节点存在原型链污染漏洞,用户可控的 schemaName 字段被直接当作对象键使用。攻击者传入 __proto__ 就能污染全局 Object.prototype,再配合 Git 节点的 GIT_SSH_COMMAND 环境变量,整条链可以打到远程代码执行。本文拆解攻击路径、PoC 和修复方案,顺便聊聊为什么这类漏洞总是出现。

发现过程

审计 n8n 节点实现的时候,我一直在找一种模式:用户输入的字符串被直接当成普通对象的属性键,而且没有先检查 __proto__constructorprototype 这几个危险值。在 nodes-base 包里 grep 了一圈,GSuiteAdmin 节点马上跳了出来。

这个节点处理用户创建和更新操作时,有一个”自定义字段”功能。Schema 名称、字段名、值全部来自工作流配置。只要攻击者有编辑器权限,这三个值就完全可控。Schema 名称被直接拿来做动态键分组字段:

customSchemas[schemaName] ??= {}; (customSchemas[schemaName] as IDataObject)[fieldName] = value;

问题就这两行。如果 schemaName 是 "__proto__",写入的目标就变成了 Object.prototype

技术细节

漏洞代码

GSuiteAdmin 节点在两个地方有相同代码,一处是用户创建操作(520-521 行),一处是更新操作(802-803 行):

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

   customSchemas[schemaName] ??= {};                              // (1)    (customSchemas[schemaName] as IDataObject)[fieldName] = value; // (2) });

schemaName"__proto__" 时发生的事:

  1. customSchemas["__proto__"] 触发了 __proto__ getter,返回 Object.prototype。它不是 nullish,所以 ??= 赋值直接跳过
  2. (Object.prototype)[fieldName] = value 把攻击者控制的字符串写到了全局对象原型上

这之后创建的每一个普通对象都会继承这个被污染的属性。

从污染到代码执行

原型链污染本身就够麻烦了——TypeORM 会因为这个直接崩溃,后面细说。但它还能进一步变成完整的 RCE,用的正是我之前在 XML 节点报告里找到的那个 gadget。

整条链是这样的:

  1. simple-git 创建一个普通的 env 对象。当 Git 节点调用 .env() 时,simple-git 分配了 {} 来存环境变量。这个对象继承了 Object.prototype
  2. Node.js 的 spawn() 继承了被污染的属性。构建子进程环境时,Node.js 遍历 env 对象的属性——包括从原型链继承来的。
  3. Git 会执行 GIT_SSH_COMMAND。当 git 遇到 SSH 风格的 URL 时,它会把 GIT_SSH_COMMAND 当 shell 命令执行。如果我们污染了 Object.prototype.GIT_SSH_COMMAND,这个值会传播到 git 子进程里,然后被执行。

完整攻击路径:Webhook → GSuiteAdmin(污染)→ Git(RCE)。

PoC

工作流配置:

  1. Webhook 节点,POST /rce
  2. GSuiteAdmin 节点,Resource: User,Operation: Create。把自定义字段的 schema name、field name、value 设为从 Webhook 请求体读取的表达式
  3. Git 节点,Operation: Clone,指向一个 SSH URL

一条 HTTP 请求触发整条链:

curl -X POST “https://TARGET/webhook/rce” \  -H “Content-Type: application/json” \  -d ‘{    “schemaName”: “__proto__”,    “fieldName”: “GIT_SSH_COMMAND”,    “value”: “sh -c ‘\”id; cat /etc/passwd’\” –”  }’

GSuiteAdmin 节点在 Google API 调用阶段会失败——这无所谓,污染发生在请求发出之前。然后 Git 节点用被污染的 GIT_SSH_COMMAND 执行 git clone,攻击者的命令以 n8n 进程用户的身份运行。

DoS 副作用

就算没有 RCE 链,污染本身也足够破坏力。Object.prototype 被污染后,TypeORM 的 buildWhere 函数在 for...in 迭代时会捡到这些额外属性,然后每次数据库查询都抛 EntityPropertyNotFoundError。n8n 的 UI 直接失去响应,所有工作流执行失败,必须完整重启才能恢复。

影响范围

  • 所有部署类型(自托管、worker 模式、Cloud)都受影响,可以实现以 n8n 进程用户身份的远程代码执行
  • 凭据全部泄露——n8n 进程持有所有已存凭据的加密密钥
  • 完全拒绝服务——TypeORM 崩溃循环让实例不可用,直到重启

为什么这类问题总是出现

说实话,看到这个漏洞我不意外。用户输入直接当对象键使用,这个模式在 JavaScript 项目里太常见了。开发者默认用户输入是安全的字符串,但 JavaScript 的 __proto__ 就是一颗地雷。你永远不知道哪个属性名会触发原型链。

更让人无语的是,n8n 自己的代码库里已经有一个 deepMerge 工具函数,里面做了原型链污染的防护。GSuiteAdmin 节点只是没有用它。这不是什么需要新技术才能发现的问题,就是纯代码审查遗漏。

RCE 链本身要依赖 Git 节点存在,不是所有实例都会中招。但 DoS 那部分是通用的——污染一旦发生,TypeORM 直接崩。所以就算不考虑 RCE,这个漏洞也应该尽快修。

还有一个需要注意的点:攻击前提是编辑器权限。在 n8n 的默认模型里,工作流编辑权限通常授予整个团队成员,而不是只有管理员。这意味着在很多实际部署中,普通成员就能触发这条链。如果你是自托管用户,建议检查一下谁有编辑器权限。

修复方案

修复很简单:把用户输入当对象键用之前,先拒绝危险属性名。对 schemaName__proto__constructorprototype 的黑名单检查,或者直接用

参考资料

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


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:幻泉之洲 《一个 Schema 名称如何变成 n8n 的 RCE》

评论:0   参与:  0