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=

相关推荐
咖啡星人k8 分钟前
AI 编程工具集体变脸:Cursor 漏洞、TRAE 限额、Claude Code 数据收集风波
人工智能·安全·microsoft·cursor·trae·ai编程工具
凡人AI录1 小时前
GPT-5.6 正式发布:Codex 并入 ChatGPT,额度与模型体系全面调整
人工智能·gpt·chatgpt
墨舟的AI笔记1 小时前
组件级响应式的破局:CSS 容器查询的工程落地与取舍
人工智能
Molesidy1 小时前
【AI】【ChatGPT】【Codex】codex桌面版的插件(MCP)和技能(skills)的安装和应用
人工智能·codex·ai agent
工业设备方案笔记1 小时前
AI大模型为什么开始跑到边缘设备上?——本地大模型部署正在成为新的行业趋势
人工智能
未知违规用户2 小时前
大模型项目:RAG项目实战与FlagEmbedding模型
人工智能·windows·python·深度学习
蓝狐社2 小时前
宁德时代不是电池厂,是下棋的
人工智能
李燚8 小时前
RAG 流水线设计:Eino 的 Loader → Transformer → Indexer → Retriever(第60篇-E46)
人工智能·深度学习·transformer·agent·rag·aiagent·eino
码农学院8 小时前
Vue3小程序开发实战:服装鞋帽箱包行业库存同步方案
人工智能
To_OC8 小时前
跟 AI 写代码越写越乱?我靠这套「Vibe Coding」思路彻底治好了幻觉屎山
人工智能·agent·vibecoding