1. 项目概述:当工作流自动化遇上代码沙箱逃逸
最近在梳理一些开源自动化工具的安全边界时,一个编号为CVE-2025-68668的漏洞引起了我的注意。这个漏洞发生在n8n这个近年来非常流行的开源工作流自动化平台上,核心问题出在它集成的Pyodide组件上,最终导致了远程命令执行。对于任何在生产环境中部署了n8n,尤其是允许用户自定义JavaScript代码节点的团队来说,这个漏洞的潜在风险不容小觑。简单来说,攻击者可以利用这个漏洞,通过精心构造的输入,让n8n服务端执行任意系统命令,从而完全控制服务器。今天,我就结合自己的复现过程,把这个漏洞的来龙去脉、触发条件、利用方式以及关键的防护思路,给大家掰开揉碎了讲清楚。无论你是n8n的运维人员、安全研究员,还是对自动化工具安全机制感兴趣的开发者,这篇文章都能帮你建立起对这个漏洞的完整认知。
n8n本身是一个强大的、基于节点的可视化工作流工具,它允许用户通过拖拽连接不同的节点(如HTTP请求、数据库操作、逻辑判断等)来构建复杂的自动化流程。为了提供极致的灵活性,n8n内置了“Code”节点,支持用户编写JavaScript或Python(通过Pyodide)代码来处理数据。Pyodide是一个将Python运行时编译到WebAssembly并在浏览器中运行的项目,n8n利用它实现了在Node.js服务端环境中安全地执行用户提交的Python代码。然而,CVE-2025-68668正是打破了Pyodide试图构建的这层“安全沙箱”。在深入技术细节之前,我们先明确一点:这个漏洞的利用前提是攻击者已经拥有在n8n工作流中创建或编辑“Code”节点的权限。这通常意味着攻击者要么是一个具有相应权限的已登录用户,要么通过其他漏洞(如CSRF、权限提升)获得了这个能力。因此,它的威胁模型更偏向于“内部威胁”或“权限滥用后的横向移动”。
2. 漏洞原理深度拆解:Pyodide沙箱的“裂缝”
要理解CVE-2025-68668,我们必须先搞明白n8n中Pyodide的工作机制以及它理想中的安全边界。这不是一个简单的输入验证绕过,而是涉及到底层能力暴露的沙箱逃逸问题。
2.1 n8n中Pyodide的集成架构
在n8n的架构中,当你在一个“Code”节点中选择使用Python时,你写的代码并不会被直接发送到服务器的原生Python解释器中执行。相反,n8n会在一个独立的Node.js子进程里启动一个Pyodide实例。Pyodide核心是一个WebAssembly模块,它包含了一个完整的、功能受限的CPython解释器。用户提交的Python代码就在这个WASM沙箱环境中被解释执行。设计初衷是美好的:WASM内存隔离、无直接系统调用能力,理论上应该很安全。n8n的Pyodide执行环境还预先注入了一些有用的对象,比如output用于输出数据,以及一个_对象(通常指lodash库)等,方便用户脚本使用。
然而,Pyodide为了使其在浏览器外(如Node.js)也能工作,并提供一些必要的交互能力,它暴露了一个名为pyodide的全局对象,或者通过特定API可以访问到一些底层功能。关键点在于,Pyodide提供了与宿主环境(Node.js)进行交互的通道。一个重要的接口是pyodide.runPython或pyodide.runPythonAsync,它们不仅能运行代码,还能在Python和JavaScript之间传递对象。更危险的是,Pyodide允许JavaScript代码调用Python函数,反之亦然,并且可以访问一些“内建”模块。
2.2 漏洞核心:被暴露的importlib与sys模块
漏洞的根源在于,在n8n集成的Pyodide环境中,攻击者可以通过Python代码访问到本应被严格限制或清理的模块,特别是importlib和sys。在标准的浏览器Pyodide环境中,许多可能用于逃逸的模块(如os,subprocess)要么不存在,要么功能被阉割。但importlib是Python导入系统的核心,如果它能被完整使用,就可能成为一个“万能钥匙”。
通过importlib,攻击者可以尝试导入不在默认沙箱白名单中的模块。而sys模块则提供了对Python解释器运行时环境的访问,包括sys.modules字典(所有已加载模块的缓存)和sys.path(模块搜索路径)。在完整的CPython中,通过sys可以接触到许多底层接口。在n8n的漏洞版本中,攻击者编写的恶意Python脚本能够:
- 利用
importlib.import_module或__import__函数,尝试导入os、subprocess、socket等敏感模块。 - 由于Pyodide运行在Node.js子进程中,而Node.js子进程本身具有执行系统命令的能力(通过
child_process模块),一旦Python层能够触碰到这些能力,沙箱就被打破了。 - 一种典型的利用链是:在Python中导入
os模块,然后调用os.system()或os.popen()。在普通的Pyodide for Web中,这些调用会失败或不存在。但在n8n的特定集成环境下,由于Pyodide与Node.js宿主环境的特殊绑定关系,这些调用可能被桥接到宿主环境,最终导致在运行n8n服务的服务器上执行命令。
注意:复现的具体利用方式可能因n8n和Pyodide的版本而异。有些利用可能需要通过
sys模块修改某些属性,或利用Pyodide提供的js对象(用于访问JavaScript命名空间)来间接调用Node.js的child_process模块。核心思想始终是:从Python沙箱内部,找到一条路径来调用宿主Node.js环境的系统命令执行功能。
2.3 与常见漏洞模式的对比
这个漏洞不同于典型的“模板注入”或“代码注入”。它不是将未过滤的用户输入拼接进动态执行的代码字符串(如eval(input))。相反,它是在一个预期允许执行代码的上下文(Code节点)中,利用了沙箱本身的不完整性。沙箱提供了执行Python的能力,但未能彻底切断所有通向危险底层功能的路径。这更像是一种“权限配置错误”或“安全边界模糊”,属于设计或实现上的缺陷,而非简单的输入过滤疏忽。这也使得传统的WAF(Web应用防火墙)基于特征匹配的规则很难防御,因为攻击载荷本身就是合法的Python代码片段。
3. 漏洞复现环境搭建与验证
理论讲得再多,不如亲手实践一遍。下面我详细记录一下在受控环境中复现CVE-2025-68668的完整过程。再次强烈警告,所有操作必须在完全隔离的虚拟机或实验环境中进行,严禁对任何非授权系统进行测试。
3.1 实验环境准备
我选择使用Docker来快速搭建一个存在漏洞的n8n环境,这是最接近真实部署场景且易于还原的方式。
- 确定漏洞版本:根据CVE描述和社区信息,该漏洞影响n8n 1.71.0及之前的一些版本。我们选择
n8nio/n8n:1.70.0这个镜像进行复现。 - 启动脆弱版n8n容器:
这里我们开启了基础认证(用户docker run -it --rm \ --name n8n-vuln \ -p 5678:5678 \ -e N8N_BASIC_AUTH_ACTIVE=true \ -e N8N_BASIC_AUTH_USER=admin \ -e N8N_BASIC_AUTH_PASSWORD=password \ -v n8n_data:/home/node/.n8n \ n8nio/n8n:1.70.0admin,密码password),并将Web服务端口5678映射到宿主机。数据卷用于持久化配置。 - 访问与登录:启动后,在浏览器中访问
http://localhost:5678,使用上面设置的用户名密码登录,进入n8n的主界面。
3.2 构造恶意工作流进行复现
复现的核心是创建一个包含恶意Python代码的“Code”节点。
- 创建新工作流:在n8n控制台,点击“New”创建一个空白工作流。
- 添加“Code”节点:从节点面板中,搜索并添加一个“Code”节点到画布上。
- 配置Code节点:
- Mode: 选择
Python。 - Code: 在代码编辑框中,输入我们的测试载荷。一个相对直接且经典的测试载荷是尝试执行系统命令。例如,我们可以尝试列出根目录文件:
import os result = os.listdir('/') output = [{"result": result}]
或者,更隐蔽地,通过# 尝试利用subprocess执行命令 try: import subprocess # 执行一个无害的命令,如打印当前工作目录 cmd_result = subprocess.check_output('pwd', shell=True, text=True) output = [{"status": "success", "output": cmd_result}] except Exception as e: output = [{"status": "error", "message": str(e)}]importlib动态导入:import importlib # 尝试导入os模块并执行命令 os = importlib.import_module('os') # 执行id命令查看当前进程用户 user_info = os.popen('id').read() output = [{"user_info": user_info}] - Mode: 选择
- 执行与观察:点击节点右上角的“Execute Node”按钮。如果环境存在漏洞,节点将成功执行,并在“Output”面板看到命令执行的结果(例如,当前工作目录路径或用户ID信息)。如果沙箱是完好的,你通常会看到一个导入错误(如
ModuleNotFoundError: No module named 'os')或一个执行被禁止的错误。
实操心得:在实际测试中,由于Pyodide版本和n8n封装的具体差异,直接导入
os或subprocess可能不会成功。有时需要更迂回的方式,例如通过sys模块找到已加载的模块,或者利用Pyodide提供的js全局对象来访问Node.js的require函数,进而加载child_process模块。这需要一些对Pyodide和Node.js交互机制的了解。一个更高级的测试载荷可能长这样:import sys # 检查sys.modules中是否有可利用的模块 output = [{"modules": list(sys.modules.keys())}]通过这个输出,你可以观察沙箱内实际可用的模块列表,从而寻找突破口。
3.3 复现结果分析与验证
如果测试载荷执行成功并返回了系统命令的结果,那么漏洞复现就成功了。这证明了:
- 沙箱逃逸发生:Python代码成功突破了Pyodide的WASM沙箱限制。
- 命令执行实现:代码获得了在宿主操作系统(运行n8n Docker容器的系统)上执行命令的能力。
- 风险确认:攻击者可以利用此功能执行任意命令,包括但不限于:读取敏感文件(
/etc/passwd,~/.n8n/config)、植入后门、进行内网横向移动、加密文件进行勒索等。
你应该记录下成功的载荷、n8n的版本号、Pyodide的版本号(如果能在代码中获取到)以及确切的输出结果。这些信息对于后续分析漏洞影响范围和修复方案至关重要。
4. 漏洞利用链的深入分析与拓展
仅仅复现命令执行还不够,作为一个安全研究者或运维人员,我们需要理解漏洞的完整利用链,评估其真正的杀伤力。
4.1 从代码执行到持久化后门
一个简单的ls或id命令证明漏洞存在,但真实的攻击会走得更远。假设攻击者已经通过某种方式(如钓鱼获取凭证、利用其他漏洞)获得了在n8n中编辑工作流的权限,他可以利用CVE-2025-68668做以下事情:
- 信息收集:执行
ifconfig、netstat -tulnp、env、cat /proc/version等命令,摸清服务器环境、网络配置、运行的服务。 - 窃取敏感数据:n8n通常用于集成各种API和数据库,其配置中可能存储了大量密钥、令牌和数据库凭证。攻击者可以编写Python代码遍历服务器文件系统,寻找包含
key、secret、password、.env、.n8n等关键词的文件并外传。 - 植入持久化后门:直接在服务器上写入一个Web Shell(如一个简单的PHP或Python HTTP服务),或者创建一个反向Shell连接到攻击者控制的服务器。例如,通过Python代码下载并执行一个静态链接的二进制后门,或者利用n8n本身的工作流调度功能,创建一个定时执行恶意命令的“计划任务”。
- 横向移动:如果n8n服务器在内网中,攻击者可以利用它作为跳板,扫描和攻击内网中的其他机器。他们可以通过Python脚本调用
nmap、sshpass等工具(如果服务器上安装了的话),或者直接使用Python的socket、paramiko(如果沙箱允许导入)等库进行内网渗透。
4.2 利用场景与权限边界探讨
这个漏洞的利用有一个关键前提:攻击者需要具备在目标n8n实例上创建或修改工作流,并能执行包含Code节点的权限。这通常对应以下场景:
- 内部威胁:心怀不满或有恶意的员工,其账号本身就有编辑工作流的权限。
- 供应链攻击:如果n8n被集成为一个公共服务的一部分,且该服务允许用户自定义工作流(例如某些低代码平台),那么任意注册用户都可能成为潜在攻击者。
- 组合利用:攻击者首先通过其他漏洞(例如,n8n的另一个未授权访问漏洞、弱口令、或者针对管理员的社会工程学攻击)获得高级权限,然后利用此漏洞进行深度利用。
- 权限提升:如果一个低权限用户只能编辑某个特定工作流,但该工作流恰好包含一个Code节点,且输入参数部分存在注入点(虽然可能性较低),也可能构成攻击路径。
因此,在评估风险时,不能孤立地看这个漏洞,而要将其放在整个应用的安全上下文中。一个严格遵循最小权限原则、对工作流编辑进行严格审计和审批的n8n部署,受此漏洞的影响会小很多。反之,一个允许所有用户自由创建和执行任意工作流的公开n8n实例,风险则是灾难性的。
4.3 漏洞的变种与绕过思路
在修复补丁发布后,攻击者可能会寻找新的绕过方法。可能的思路包括:
- 寻找未禁用的危险函数:如果补丁只是黑名单禁用了
os和subprocess,那么攻击者可能会寻找其他可以执行命令的模块或函数,例如pty、commands(Python 2)、或者通过ctypes模块直接调用libc的system函数(如果Pyodide环境编译时包含了这些库)。 - 利用JavaScript桥接:Pyodide的
js对象是通往JavaScript世界的桥梁。如果Node.js宿主环境中的某些全局对象或函数可以通过js访问,并且这些函数能导致命令执行(例如,不小心暴露了require('child_process').exec),那么这将成为新的逃逸路径。 - 文件系统操作:即使不能直接执行命令,如果能进行任意文件读写,攻击者也可以造成严重破坏,例如覆盖关键配置文件、写入计划任务(crontab)、或者写入SSH公钥等。
- 内存破坏或WASM漏洞:理论上,如果Pyodide的WASM运行时或CPython编译到WASM的部分存在内存安全漏洞,也可能导致沙箱被彻底打破。但这属于更高阶的攻击方式。
5. 漏洞修复方案与加固建议
复现和研究漏洞的最终目的是为了修复和防御。针对CVE-2025-68668,我们可以从官方升级、配置加固、架构优化三个层面来应对。
5.1 官方补丁与版本升级
这是最直接、最推荐的解决方案。n8n官方在获悉漏洞后,会发布安全修复版本。
- 确定修复版本:关注n8n的官方GitHub仓库的安全公告或发布页面。通常,CVE编号会关联到具体的修复提交或版本号。例如,修复可能包含在
n8n@1.71.1或更高的版本中。 - 升级n8n:
- Docker用户:更新你的Docker镜像标签到最新稳定版。
然后重新创建容器。docker pull n8nio/n8n:latest # 或指定一个已知修复的版本 docker pull n8nio/n8n:1.71.1 - npm/docker-compose/裸机安装用户:按照官方升级指南进行操作,通常涉及更新软件包或替换二进制文件。
- Docker用户:更新你的Docker镜像标签到最新稳定版。
- 验证修复:升级后,重复上述复现步骤。使用之前成功的攻击载荷进行测试,此时应该看到命令执行失败,并返回明确的权限错误或模块未找到错误。这是验证修复是否生效的最佳方式。
5.2 运行时安全加固配置
即使打了补丁,从深度防御的角度,我们也应该对n8n的运行环境进行加固。
- 严格遵循最小权限原则:
- 运行用户:绝不要以
root用户运行n8n。在Docker中,使用-u参数指定非root用户(如node)。在宿主机上,创建一个专用低权限用户来运行n8n服务。 - 文件系统权限:将n8n的数据目录、配置目录的权限设置为仅运行用户可读写。使用Docker卷或宿主机目录挂载时,注意所有权和权限。
- 运行用户:绝不要以
- 容器化隔离:使用Docker或Podman等容器技术部署n8n,可以利用内核的命名空间、cgroups等机制进行资源隔离。确保容器以只读根文件系统(
--read-only)运行,并以非特权模式运行。 - 网络隔离:将n8n部署在内网,严格限制其出站和入站连接。如果n8n需要访问外部API,通过白名单机制控制出站流量。使用反向代理(如Nginx)对外暴露,并配置严格的WAF规则(尽管对这类漏洞可能效果有限,但能防御其他常见Web攻击)。
- 审计与监控:
- 工作流审计:定期审查n8n中的工作流,特别是那些包含Code节点的工作流。关注是否有未知或可疑的代码被添加。
- 系统监控:在运行n8n的服务器上部署HIDS(主机入侵检测系统),监控异常进程创建、可疑命令执行和文件系统改动。集中收集和分析n8n的应用日志。
- 访问控制:
- 强化认证:启用并强制使用多因素认证(MFA)来保护n8n的管理员和用户账户。
- 权限细分:仔细规划n8n内的用户角色和权限。除非绝对必要,不要给普通用户“可以编辑和执行任何工作流”的权限。考虑使用工作流版本控制和审批流程。
5.3 针对代码节点的安全策略
对于必须使用Code节点的场景,可以考虑以下主动防御措施:
- 代码审查与静态分析:如果团队规模允许,建立对生产环境工作流中Code节点代码的审查流程。可以尝试集成简单的静态分析工具,扫描代码中是否包含明显危险的字符串(如
import os,subprocess,eval,exec等),但这只是一种辅助手段,很容易被绕过。 - 使用替代方案:评估是否真的需要内联Python代码。许多逻辑可以通过串联多个内置节点(如Function节点、IF节点、HTTP Request节点)来实现。n8n的Function节点(JavaScript)在沙箱安全性上相对更好一些(运行在VM2等沙箱中),但也不是绝对安全,同样需要关注相关漏洞。
- 沙箱强化(高级):对于有深厚技术能力的团队,可以考虑定制Pyodide的构建,移除或禁用所有不必要的、潜在危险的Python模块和C扩展。或者,探索使用更严格的沙箱技术(如gVisor、Firecracker microVM)来隔离整个n8n进程。但这会带来显著的复杂性和性能开销。
6. 排查与应急响应指南
如果你怀疑自己的n8n实例已经遭受利用此漏洞的攻击,或者在进行安全评估时发现了可疑迹象,可以按照以下步骤进行排查和应急响应。
6.1 入侵迹象识别
以下是一些可能表明n8n实例被利用的迹象:
- 异常工作流:管理界面中出现名称奇怪、描述不清、或包含大量Code节点的新工作流。特别是那些被设置为“激活”状态,定期自动执行的工作流。
- 未知的代码内容:检查现有工作流中的Code节点,查看Python代码部分是否被篡改,插入了可疑的
import语句、系统命令调用(os.system,subprocess.call)、网络连接代码(socket、requests)或文件操作代码。 - 系统资源异常:服务器出现异常的CPU、内存或网络流量飙升,尤其是在n8n进程相关的时段。
- 未知进程或连接:在服务器上使用
ps aux | grep n8n或netstat -tulnp查看是否有n8n进程发起的异常子进程,或对外部可疑IP地址的连接。 - 文件系统改动:在n8n数据目录、系统临时目录或Web根目录下发现陌生的可执行文件、脚本文件或Webshell文件。
- 安全日志告警:如果部署了HIDS或监控系统,可能会收到关于异常命令执行、敏感文件读取或反弹Shell连接的告警。
6.2 应急响应步骤
一旦确认或高度怀疑存在入侵,应立即采取以下措施:
- 立即隔离:
- 网络隔离:在防火墙上阻断该服务器对所有非必要IP的访问,特别是出站连接,防止数据外泄或攻击者维持控制。
- 服务下线:停止n8n的Docker容器或服务进程。注意:如果为了取证,在停止前可能需要先创建内存转储。
- 取证与调查:
- 备份现场:对整个n8n的数据目录(通常是
~/.n8n或Docker卷挂载点)、Docker容器(如果使用)进行完整备份。备份系统日志(/var/log/)、命令历史(.bash_history)等。 - 分析工作流:在隔离环境中,仔细检查备份出来的n8n数据库(通常是SQLite)或JSON文件,查找所有被修改或新增的、包含Code节点的工作流。提取其中的恶意代码。
- 分析日志:检查n8n的应用日志,寻找代码执行相关的错误信息或成功记录。检查系统日志,寻找与n8n进程UID相关的可疑活动。
- 排查持久化:检查系统的计划任务(crontab)、服务(systemd)、启动项、SSH授权密钥等位置,看攻击者是否植入了后门。
- 备份现场:对整个n8n的数据目录(通常是
- 清除与恢复:
- 清除后门:根据取证结果,删除所有恶意文件、进程、计划任务和账户。
- 重置凭证:重置n8n数据库中的所有用户密码、所有通过n8n集成的第三方服务的API密钥、令牌和密码。
- 恢复服务:在完成漏洞修复前,切勿恢复服务!首先,将n8n升级到已修复CVE-2025-68668的安全版本。然后,从干净的备份(确信入侵发生前的备份)中恢复工作流数据。如果备份不可用或不可信,则需要手动审查和清理每一个工作流。最后,在新的、加固过的环境中重新部署服务。
- 复盘与加固:分析入侵根本原因(是弱口令、其他漏洞导致权限获取,还是内部威胁?),修复安全短板,并实施前面章节提到的加固建议,尤其是强化认证、权限控制和监控。
6.3 漏洞修复验证清单
在完成修复和加固后,建议执行一个简单的验证清单,确保漏洞已被有效封堵:
| 检查项 | 操作方法 | 预期结果 |
|---|---|---|
| 版本确认 | 登录n8n,查看设置中的版本号。 | 版本号应高于修复版本(如1.71.1)。 |
| 基础漏洞复现 | 创建一个新的Python Code节点,尝试执行包含import os; os.system(‘id’)的代码。 | 应执行失败,并返回模块导入错误或安全错误,而非命令执行结果。 |
| 模块导入测试 | 在Code节点中尝试导入subprocess,socket,ctypes,importlib等敏感模块。 | 应返回ModuleNotFoundError或明确的访问拒绝错误。 |
| 文件读写尝试 | 尝试使用open(‘/etc/passwd’, ‘r’).read()读取系统文件。 | 应因权限不足或沙箱限制而失败。 |
| 权限检查 | 检查运行n8n进程的系统用户(如 `ps aux | grep n8n`)。 |
| 网络隔离 | 从n8n服务器尝试访问一个外部地址(如curl https://ifconfig.me)。 | 应根据网络策略,最好无法访问或访问被限制。 |
通过以上系统的复现、分析和加固,我们不仅理解了CVE-2025-68668这个特定漏洞的原理与危害,更重要的是建立起了一套针对n8n这类可编程自动化工具的安全评估与防护思路。安全是一个持续的过程,尤其是在使用如此灵活强大的工具时,时刻保持警惕,遵循最小权限和深度防御原则,才能让自动化真正为业务赋能,而非带来风险。