Flowise预认证任意文件上传漏洞分析(CVE-2025-26319)

漏洞概述

Flowise 是一个广泛使用的开源无代码/低代码平台,旨在简化不具备高级技术技能的用户创建 AI 代理的过程。它提供拖放式界面,方便用户配置知识库、工具和模型。

Flowise 拥有超过 35K 的 GitHub Star 和 1M+ 的 Docker 拉取量,用户范围涵盖中小型企业到大型企业。

在探索使用 Flowise 平台开发 AI 代理的过程中,我们发现了一个严重漏洞(CVE-2025-26319)。该漏洞允许未认证攻击者通过"知识上传"功能,向托管 AI 代理的服务器上传任意文件。攻击者可利用此漏洞上传恶意文件、脚本、配置文件甚至 SSH 密钥,从而获取整个服务器的远程控制权。

技术细节

在 Flowise 系统中,某些 API 被有意设置为无需认证即可访问,这些 API 被归类在白名单 WHITELIST_URLS 中。

javascript 复制代码
export const WHITELIST_URLS = [
    '/api/v1/verify/apikey/',
    '/api/v1/chatflows/apikey/',
    '/api/v1/public-chatflows',
    '/api/v1/public-chatbotConfig',
    '/api/v1/prediction/',
    '/api/v1/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'
]

当服务器收到新请求时,系统会检查 URL 是否在白名单中。若在白名单内,请求继续处理;否则,系统要求进行身份验证。

javascript 复制代码
// @ /packages/server/src/index.ts
this.app.use(async (req, res, next) => {
    // Step 1: Check if the req path contains /api/v1 regardless of case
    if (URL_CASE_INSENSITIVE_REGEX.test(req.path)) {
        // Step 2: Check if the req path is case sensitive
        if (URL_CASE_SENSITIVE_REGEX.test(req.path)) {
            // Step 3: Check if the req path is in the whitelist
            const isWhitelisted = whitelistURLs.some((url) => req.path.startsWith(url))
            if (isWhitelisted) {
                next() // continue
            } else if (req.headers['x-request-from'] === 'internal') {
                basicAuthMiddleware(req, res, next)
            } else {
                const isKeyValidated = await validateAPIKey(req)
                if (!isKeyValidated) {
                    return res.status(401).json({ error: 'Unauthorized Access' })
                }
                next()
            }
        } else {
            return res.status(401).json({ error: 'Unauthorized Access' })
        }
    } else {
        // If the req path does not contain /api/v1, then allow the request to pass through, example: /assets, /canvas
        next()
    }
})

现在,让我们仔细检查 /api/v1/attachments 路由。

此 API 路由用于处理最终用户上传附件供代理处理。例如,用户可以上传图像并要求代理或聊天机器人描述图片内容。

javascript 复制代码
// @ /packages/server/src/routes/attachments/index.ts
const router = express.Router()

// CREATE
router.post('/:chatflowId/:chatId', getMulterStorage().array('files'), attachmentsController.createAttachment)

export default router

经过多次调用后,请求最终到达 createFileAttachment 函数。

最初,该函数从请求中获取 chatflowidchatid,但未进行任何额外验证。唯一检查是确保这些参数存在于请求中。

javascript 复制代码
// @ /packages/server/src/utils/createAttachment.ts
// @ createFileAttachment function
const chatflowid = req.params.chatflowId
if (!chatflowid) {
    throw new Error('Params chatflowId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId')
}
const chatId = req.params.chatId
if (!chatId) {
    throw new Error('Params chatId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId')
}

接下来,该函数检索上传的文件,并尝试通过调用 addArrayFilesToStorage 函数将它们添加到存储中。

javascript 复制代码
// @ /packages/server/src/utils/createAttachment.ts
// @ createFileAttachment function
const files = (req.files as Express.Multer.File[]) || []
const fileAttachments = []
if (files.length) {
    // ...
    for (const file of files) {
        const fileBuffer = await getFileFromUpload(file.path ?? file.key) // get the uploaded file
        const fileNames: string[] = []
        file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
        // add it to the storage
        const storagePath = await addArrayFilesToStorage(
            file.mimetype,
            fileBuffer,
            file.originalname,
            fileNames,
            chatflowid,
            chatId
        )
        // add it to the storage
        // ...
        await removeSpecificFileFromUpload(file.path ?? file.key) // delete from tmp
        //  ...
        fileAttachments.push({
            name: file.originalname,
            mimeType: file.mimetype,
            size: file.size,
            content
        })
    } catch (error) {
        throw new Error(`Failed operation: createFileAttachment - ${getErrorMessage(error)}`)
    }
}
return fileAttachments

现在,我们来看一下 addArrayFilesToStorage 函数。

javascript 复制代码
// @ /packages/components/src/storageUtils.ts
// @ addArrayFilesToStorage function
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) // PATH TRAVERSAL.
        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)
    }
}

如代码注释所示,该函数通过将 getStoragePath 函数的输出与 ...paths(即之前从请求中提取的 chatflowidchatId)拼接来构建目录路径。

如前所述,这些值并未被验证是否为 UUID 或整数。因此,攻击者可以操纵这些变量,将 dir 变量设置为任意值。

结合文件名也由用户提供的这一点,最终导致了未认证任意文件上传漏洞。

漏洞利用演示(POC)

以下仅为所需的 HTTP 请求。

如我们所见,我们并未经过身份认证。通过操纵 chatId 参数,我们可以执行路径遍历。在此示例中,我们覆盖了 api.json 文件,该文件包含系统的 API 密钥。

在此示例中,dir 变量将解析为 /root/.flowise/storage/test/../../../../../root/.flowise/filenameapi.json。当在用户界面中检查 API 密钥时,我们可以看到它们已被修改。

漏洞影响

未认证任意文件上传可能导致严重后果,例如:

  • 危害整个代理框架
  • 获取整个服务器的远程控制权
  • 数据渗透

披露时间线

  • 2025年1月20日 --- 首次尝试联系供应商。
  • 2025年2月2日 --- 使用 GitHub 私有安全功能提交漏洞详情。
  • 2025年2月11日 --- 通过 Flowise Discord 服务器第二次尝试联系供应商。
  • 2025年2月17日 --- 通过 Flowise 邮箱地址第三次尝试联系供应商。
  • 2025年2月28日 --- 第四次尝试联系供应商,通知其将公开发布。
  • 2025年3月6日 --- 发布此博客文章。

重要说明

遗憾的是,Flowise 团队对我们的努力未作出回应。尽管我们在过去45天内尝试与他们合作修复此漏洞,但未收到任何确认。鉴于我们观察到该漏洞正被积极利用,我们艰难地决定公开披露它,以确保其他人能够采取适当的预防措施。此漏洞的解决方案简单明了,我们提供了一个补丁以帮助有兴趣解决此问题的人。

补救措施

有两种方法可以缓解此漏洞:

  1. 更改存储类型为 S3 :默认情况下,存储类型设置为 Local,这使漏洞更为严重。如果您的存储类型是 S3,则免受此类攻击。

  2. 应用我们提供的补丁:我们鼓励您事先审查补丁,因为它非常小且直观,您可以放心应用。

您可以在此处获取漏洞补丁。

如果您想在应用补丁后验证您的服务是否保持安全,请在 uncoveragent.com 留下您的详细信息。 CSD0tFqvECLokhw9aBeRqpnsY1rOFcBy+9txYADKsO6F9Pf3nieYy9++rTT+gJJQZ4OJKjXH5GuyaxkaaHya2+NIG5umwLcKj6pmJjyiP2ve0eFKxaJOJKnMGl0HSUa19EO0ZpdbBf967/m0JqZ8MvXpZhj/OSiAB4dGHsUQGDA=

相关推荐
shushangyun_1 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
闵孚龙1 小时前
《PyTorch 深度修炼》Dataset 和 DataLoader:数据如何喂给模型
人工智能·pytorch·python
双斜杠少年1 小时前
万字长文一文入门AI agent开发《AI agent开发相关概念》
人工智能
AI产品测评官1 小时前
Moka与北森用户如何接入世纪云猎,搭建完整AI招聘寻访链路
人工智能
qq_366566501 小时前
2026最新:5款AI视频口型同步工具实测横评,视频翻译后嘴型对不上的终极解决方案
人工智能·计算机视觉·新媒体运营
ofoxcoding1 小时前
在AI API聚合平台配置DeepSeek V3.2提示词缓存实战:快速接入与成本优化指南
人工智能·spring·缓存·ai
Godspeed Zhao1 小时前
Level 4自动驾驶系统设计3——功能与场景3
人工智能·机器学习·自动驾驶
weixin_397574092 小时前
PDF复杂表格的1:1还原引擎:跨页表格自动拼接技术实战
大数据·人工智能·pdf
DigitalOcean2 小时前
砍掉 60% AI 推理成本:深度解构 DigitalOcean 推理路由器的 MoE 门控与智能分流机制
llm·aigc·agent