CVE-2026-0863
利用字符串格式化和异常处理,攻击者可以绕过 n8n 的 python-task-executor 沙箱限制,并在底层操作系统中运行任意不受限制的 Python 代码。
具有基本权限的已认证用户可以通过代码块利用此漏洞,并可能导致在“内部”执行模式下运行的实例完全接管 n8n 实例。
影响版本:
(,1.123.14)
[2.0.0,2.3.5)
[2.4.0,2.4.2)
EXP
创建一个Code组件
exp:
1 | def new_getattr(obj, attribute, *, Exception): |
成功RCE
原理分析
辅助函数 new_getattr
1 | f'{{0.{attribute}.Z3r4y}}'.format(obj) |
这里其实混用了两种字符串格式化机制: f-string 和 str.format()。
第一层:f-string 的求值
首先,Python 解释器处理最外层的 f-string f’…’ 。
假设我们调用 new_getattr(my_obj, ‘class’, …) ,那么 attribute 变量的值是字符串 “class” 。
f-string 会把 {attribute} 替换为它的值
此时,我们得到了一段 新的格式化模板字符串 : ‘{0.class.Z3r4y}’ 。
第二层:str.format() 的执行
接下来,代码执行 .format(obj) :
1 | '{0.__class__.Z3r4y}'.format(obj) |
这里的 0 指代 .format() 参数列表中的第 0 个参数,也就是 obj 。
str.format() 引擎开始解析这个模板:
- 取值 :拿到第 0 个参数 obj 。
- 属性访问 1 :解析 .class 。引擎会执行 getattr(obj, ‘class’) 。
关键点 :这一步是在 C 语言层面的格式化引擎中发生的, 能绕过 Python 层面重写的 getattribute 钩子或沙箱的静态代码审计 。沙箱可能禁止你写 obj.class ,但没禁止你写字符串。此时,引擎拿到了 obj 的类对象(比如 <class ‘ValueError’> )。 - 属性访问 2 (陷阱) :解析 .Z3r4y 。引擎继续尝试访问上一步结果的 Z3r4y 属性。
引擎执行类似 getattr(<class ‘ValueError’>, ‘Z3r4y’) 的操作,显然,ValueError 类没有叫 Z3r4y 的属性。
当第 3 步访问不存在的 .Z3r4y 属性时,Python 抛出 AttributeError,系统抛出的 e (AttributeError) 对象中, e.obj 就变成了 用户经new_getattr函数输入的obj.attribute
获取 Traceback
1 | try: |
有了 Traceback,就意味着我们可以访问程序的调用栈。
获取栈帧
1 | # 从 Traceback 中偷出 tb_frame (当前的栈帧对象) |
栈帧 (Frame) 包含了当前代码执行环境的所有信息,包括局部变量、全局变量和 内置函数 。
获取 Builtins
1 | # 从栈帧中偷出 f_builtins (内置函数字典) |
f_builtins 里包含了 import 、 open 、 exec 等所有内置函数。
最终经过构造成功RCE
官方FIX
https://github.com/n8n-io/n8n/commit/b73a4283cb14e0f27ce19692326f362c7bf3da02
ban掉了一些关键词