← 返回日报
精读 预计 3 分钟

CVE-2026-33696: From a Schema Name to RCE in n8n

摘要

作者披露了 n8n 的一个严重漏洞(CVE-2026-33696,CVSS 9.4)。漏洞根因是 GSuiteAdmin 节点在处理用户自定义字段时,将用户可控的 schema name 直接用作对象键,且未过滤 `__proto__`、`constructor` 等危险属性名。攻击者可将 schema name 设为 `__proto__` 以污染 Object.prototype,进而通过 Git 节点利用 `GIT_SSH_COMMAND` 环境变量实现远程代码执行。文中给出了完整攻击链(Webhook → GSuiteAdmin 污染 → Git 节点 RCE)、概念验证流程、DoS 副作用(TypeORM 崩溃导致实例不可用)以及修复建议(过滤危险属性名或使用 `Object.create(null)`)。漏洞影响所有部署类型,可导致 RCE、凭据窃取和拒绝服务。

荐读理由

这篇完整披露了 n8n 因未过滤 proto 键名导致的原型污染链到 RCE 的完整攻击路径,你审计自研节点或依赖时可直接照此检查同类模式

原文

critical / n8n / Aug 16, 2026 / 3 min read

From a Schema Name to RCE in n8n

n8n uses a user-supplied schema name as a bare object key. Set it to proto, pollute the prototype, chain into RCE via the Git node. One request, full shell.

CVSS

9.4 | CVE-2026-33696 GHSA-mxrg-77hm-89hv

How I Found It

While auditing n8n’s node implementations, I started looking for places where user-supplied strings end up as property keys on plain objects.

The pattern I was looking for was simple. anywhere a user-supplied string ends up as a property key on a plain object without first checking for __proto__, constructor, or prototype. I grepped through the nodes-base package and the GSuiteAdmin node stood out immediately.

The node has a “Custom Fields” section for user create and update operations. It lets you specify a schema name, field name, and value. all three come from the workflow configuration, which means an attacker with editor access controls them entirely. The schema name is used directly as a dynamic key to group fields:

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

That’s the whole bug. If schemaName is "__proto__", you’re writing to Object.prototype.

Technical Details

The Vulnerable Code

The GSuiteAdmin node handles custom schema fields in both the user create (line 520-521) and update (line 802-803) operations with identical code:

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)
});

When schemaName is "__proto__":

  1. customSchemas["__proto__"] triggers the __proto__ getter, which returns Object.prototype. it’s not nullish, so the ??= assignment is a no-op

  2. (Object.prototype)[fieldName] = value writes an attacker-controlled string directly onto the global object prototype

Every plain object created after this point inherits the polluted property.

From Pollution to Code Execution

The pollution alone is already dangerous (it crashes the entire n8n instance via TypeORM. more on that below), but it also chains into full RCE through the exact same gadget I found in the XML node report.

The chain works like this:

  1. simple-git creates a plain env object. When the Git node calls .env(), simple-git allocates {} to hold environment variables. This object inherits from Object.prototype.

  2. Node.js spawn() inherits polluted properties. When building the child process environment, Node.js iterates the env object’s properties. including inherited ones from the polluted prototype.

  3. Git respects GIT_SSH_COMMAND. When git encounters an SSH-style URL, it spawns GIT_SSH_COMMAND as a shell command. If we pollute Object.prototype.GIT_SSH_COMMAND, it propagates into the git child process and gets executed.

So the full attack is: Webhook → GSuiteAdmin (pollution) → Git (RCE).

Proof of Concept

The workflow setup:

  1. Webhook node. POST /rce

  2. GSuiteAdmin node. Resource: User, Operation: Create. Set the Custom Fields schema name, field name, and value to expressions reading from the webhook body

  3. Git node. Operation: Clone, pointed at an SSH URL

A single HTTP request fires the entire chain:

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'\'' --"
  }'

The GSuiteAdmin node fails at the Google API call (it doesn’t matter. the pollution already happened before the request was sent), and then the Git node spawns git clone with the polluted GIT_SSH_COMMAND, executing the attacker’s command as the n8n process user.

The DoS Side Effect

Even without the RCE chain, the pollution is destructive on its own. After Object.prototype is polluted, TypeORM’s buildWhere function picks up the extra properties via for...in iteration and throws EntityPropertyNotFoundError on every database query. The n8n UI goes unresponsive, all workflow executions fail, and the instance requires a full restart to recover.

Impact

  • Remote code execution as the n8n process user on all deployment types. self-hosted, worker mode, and Cloud

  • Full credential theft. the n8n process holds the encryption key for all stored credentials

  • Complete denial of service. the TypeORM crash loop makes the instance non-functional until restart

Remediation

The fix is straightforward: reject dangerous property names before using them as object keys. A blocklist check for __proto__, constructor, and prototype on the schemaName value (or using Object.create(null) for customSchemas) would prevent the pollution entirely.

n8n’s codebase already has a deepMerge utility with prototype pollution guards. the GSuiteAdmin node just wasn’t using it.

Timeline

  1. 2025-02-26

  2. Report submitted to n8n security team

  3. 2025-03-25

  4. Advisory and CVE published

--

Lobsters · 0 赞 · 0 评 讨论 → 阅读原文 →

这条对你有帮助吗?