FlawOpen

flawopen.com/Teardowns/cve-2026-105699-langflow-mcp-cross-project-file-disclosure

● CVE-2026-105699 · CVSS v4 7.1 · 高危
安全研究 · FlawOpen

CVE-2026-105699:Langflow MCP 文件越权读取

Langflow 验证了 MCP 连接,却没有逐个授权请求的文件。用户可读取他人流程文件;1.9.1 修复了所有权检查。[1], [2], [5]

CVSS 向量CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
💡 通俗易懂的原理解析 (ELI5)

想象酒店确认你有房卡后,只要报出行李牌就把任意房间的行李交给你。房卡只能证明你能进入酒店,不能证明别人的行李属于你。Langflow 的 MCP 读取必须先检查文件所有者和项目,再打开文件。[1]

核心概念与专有名词

Model Context Protocol(MCP)
AI 应用向服务器请求工具和资源的协议。服务器仍须对每个请求对象进行授权。[1]
项目范围 MCP 端点
与一个项目关联的 Langflow MCP 连接。能访问该端点不代表能访问所有项目的文件。[1]
resources/read
MCP 操作,要求服务器返回 URI 标识的资源。[1]
对象级授权
检查已认证用户是否有权访问具体记录或文件,而不只是能否访问端点。[1], [6]
CWE-639
通过用户可控键绕过授权:修改标识符即可访问其他用户的记录。[6], [2]

框架映射

  • 本指南所述弱点的 CWE 参考: CWE-639

根本原因剖析 (Root Cause)

在受影响的 Langflow 1.6.8 至 1.9.0 中,项目范围 MCP 传输只验证连接 URL 中的项目 ID。随后 resources/read 处理器接收资源 URI,解析出流程 ID 和文件名,并将它们传给 storage_service.get_file(),却没有检查流程是否属于当前已认证用户或当前项目。存储层按命名空间和文件名读取,不了解 Langflow 的用户或项目权限。因此,拥有账户且能访问自己项目 MCP 端点的用户,可以请求其他用户流程命名空间中的文件。另行记录的全局 resources/list 和 tools/list 行为可能暴露标识符、方便定位目标,但读取漏洞并不依赖枚举。它是需要认证的跨用户机密性问题,并非匿名访问或代码执行。CVE 记录给出的 CVSS 4.0 为 7.1 High,弱点为 CWE-639。Langflow 1.9.1 修复了该路径:handle_read_resource() 获取已认证用户,并要求查询同时满足 Flow.user_id == current_user.id;项目范围传输还要求 Flow.folder_id == project_id。未授权命名空间会在访问存储前被拒绝。补丁还拒绝路径分隔符和遍历序列,在读、写、删存储路径上实施目录边界检查,并把全局列表限制到当前用户。维护者的回归测试会拒绝读取其他用户的流程。[1], [2], [3], [5]

攻击执行流程分解

步骤 1

用户登录自己的项目 MCP 端点

攻击者需要 Langflow 账户,并至少能访问一个项目范围的 MCP 连接。该问题不会绕过登录。[1]

步骤 2

客户端请求资源 URI

MCP resources/read 请求携带 URI,末尾路径部分指定流程命名空间和文件名。[1]

步骤 3

存在缺陷的处理器信任标识符

handle_read_resource() 解析 ID 和文件名,却没有将流程绑定到调用用户或端点所属项目。[1], [3]

步骤 4

存储层按命名空间读取

storage_service.get_file() 返回指定命名空间中的文件。存储层没有用户所有权上下文。[1]

步骤 5

响应越过用户边界

调用方可能收到其他用户的流程文件。全局列表可以泄露标识符,但利用缺失授权并不需要列表功能。[1], [2]

源代码对比:漏洞与安全实现

这些是文档所述 Python 代码路径的重点片段,不是独立应用。存在缺陷的路径把客户端提供的命名空间当成充分权限。1.9.1 修复先在已认证用户范围内查找流程;对于项目范围 MCP 连接,还会限定当前项目,然后才允许存储层读取。补丁也会校验文件名,并在存储层强制执行路径边界,避免其他读、写、删入口绕过检查。授权应放在同时了解用户和项目的服务层;存储适配器无法自行推断所有权。 代码片段的范围:真实处理器还允许读取用户桶中的文件,但仅当命名空间等于当前已认证用户自己的 ID,且服务器不是项目范围时。片段省略了这一独立分支;不能据此推导出可访问任意命名空间。上游回归测试包含 test_handle_read_resource_denies_other_users_flow,用于断言跨用户读取会被拒绝。[1], [3], [4], [7]

源码摘录: mcp_utils.py — 对应源码片段

存在漏洞的实现
# 受影响读取路径的代码片段:解析出的 ID 被直接传给存储层。
content = await storage_service.get_file(
    flow_id=flow_id,
    file_name=filename,
)
加固后的安全修复
# 按源码结构简化展示 1.9.1 版本的授权边界。
current_user = current_user_ctx.get()
query = select(Flow).where(
    Flow.id == namespace_id,
    Flow.user_id == current_user.id,
)
if project_id is not None:
    query = query.where(Flow.folder_id == project_id)
flow = (await session.exec(query)).first()
if flow is None:
    raise ValueError("Resource not found or access denied")
content = await storage_service.get_file(
    flow_id=namespace_id,
    file_name=filename,
)

工程与系统安全加固清单

参考来源