Agent 安全笔记

2026-10-15 · 沙箱n8n漏洞分析

表达式沙箱的三轮逃逸:黑名单为什么追不上 JavaScript 规范

n8n 在三个月里被同一类缺陷绕过三次。防线从运行时推到编译期,再推到认证边界,黑名单始终慢一个版本。

一张对外公开的联系表单,填进去的字符串经模板渲染进入表达式,最后落到服务器的 shell。这类事故在编排平台上重复出现,原因不在某个补丁漏了,而在表达式求值这一层被当成了代码执行器。

沙箱与工作流跑在同一个进程

n8n 的表达式写在节点参数里,形式是 {{ ... }},节点运行时求值。代码路径在 packages/workflow/src/expression.ts,净化器来自 expression-sandboxing.ts,底层依赖 @n8n/tournament 把模板解析成 AST 再按 hook 改写。

改写发生在主进程里。PR #26077 的说法是,表达式由 tournament 在主 Node 进程内求值;迁移方向是 isolated-vm,新包已经带了 IsolatedVmBridge。进程内的沙箱没有内存边界,逃逸之后能碰到什么,取决于它和谁共享进程。

关掉 task runners 的实例更弱。官方文档写明,代码节点在主进程内运行,没有任何隔离。

黑名单假设了危险构造可以被穷举

防御思路叫 enumerate and block:改写 AST、拉运行时属性黑名单、套模板沙箱环境。三样都建立在一个假设上,危险构造可以被穷举。

这个假设在 JavaScript 上不成立。攻击者手里的指令集是语言规范本身,防御方的黑名单只是对规范的不完整枚举。规范还在增长,tournament 支持 ES6 之后,模板字符串与箭头函数进入允许面,属性表与原型链检查没有跟着变。

历史给过两次结论。Java SecurityManager 在 JDK 17 被 JEP 411 标记为待移除,JDK 24 由 JEP 486 永久禁用,替代路线写的是容器与虚拟机。vm2 在 2023 年停止维护,弃用警告写明该库存在严重安全问题、不要用于生产。Node 官方文档也写明,node:vm 不是安全机制。

三轮绕过换了三层

第一轮在 2025 年 12 月。TaggedTemplateExpression 提供模板标签,ArrowFunctionExpression 提供函数体,Error.prepareStackTrace 拿到调用栈,三个合法构造组合出读取全局对象的能力。n8n 在假期 48 小时内放出 v2.4.0,一次打了九个补丁。

第二轮在 2026 年 2 月。攻击者换到编译期,SpreadElement 在 AST 重写阶段完成逃逸,九个运行时补丁没有机会执行。根因是 rewriter 少了一个 case,process 未经变换通过。

第三轮换到认证边界。Form 节点的表达式被重复求值,未登录的访问者也能触发。

三个月内四个以上团队、六个以上 CVE。缺陷是类别性的,补丁只封住当前这条路。

控制点要前移

平台侧的方案是 policy inversion:把枚举危险节点,翻成枚举允许节点。列出工作流全部表达式,抽出用到的语法节点类型,合并成最小集合,其余类型在解析阶段拒绝。白名单按工作流生成,变更走审核。

覆盖度量要进 CI。把语言规范的节点类型清单与 rewriter 的 case 清单做差集,差集不为空就失败。校验点放在编译器之前,因为运行时补丁拦不住编译期的逃逸。

凭证隔离与前三条同等重要。求值环境只注入当前节点需要的数据集,不注入环境变量与密钥;需要访问外部系统时走受控工具调用。这样即使白名单被绕过,可达资产仍然有限。

企业侧的验收从未认证端点开始:盘点接受外部输入且进入表达式或代码节点的入口,核对厂商修复版本,检查求值环境是否注入密钥,保留带来源标记的日志。

← 返回文章列表