回环地址不是信任边界:CVE-2026-2611
2026年5月19日,National Vulnerability Database 发布了 CVE-2026-2611。MLflow 3.9.0 的 Assistant 功能在本地 /ajax-api 端点上存在来源验证不当问题。加载在任意标签页中的恶意网页,都可以向 Assistant 的本地服务器发起跨源请求,修改其配置以启用完全访问模式,随后通过内置的 Claude Code CLI 集成执行任意命令。Huntr 给出的 CVSS 3.0 评分为 9.6(CRITICAL),向量为 AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H:网络可达、复杂度低、无需权限、需要用户交互、作用域改变,并对机密性、完整性和可用性造成完全影响。1
根因并不新鲜,修复也不复杂,但威胁模型变了。任何绑定到回环地址,并向浏览器标签页暴露变更类端点的本地服务,都是信任边界表面。当这个服务驱动的是能够读取文件、运行 Shell 命令并推送代码的 AI 代理时,“只监听回环地址”就不再是有效的安全控制。随着越来越多开发者工具把 LLM 代理封装进本地 Web UI,CVE-2026-2611 将成为这一整类漏洞反复出现的教科书案例。
摘要
CVE-2026-2611 记录的是 MLflow 3.9.0 中的一个 CORS 枚举不完整漏洞。Assistant 功能暴露了 /ajax-api/ 端点,但 MLflow 的 is_api_endpoint() 检查没有覆盖这些路径,因此阻断 CORS 的中间件跳过了它们。旧版 CORSMiddleware 还允许 allow_origins=["*"],于是来自 evil.com 的跨源请求可以调用 /ajax-api/3.0/mlflow/assistant/config,把 Assistant 切换到完全访问模式,再通过内置的 Claude Code CLI 集成发送 Shell 命令。1上游修复(PR #20832,“Block CORS for ajax paths”)已在 MLflow 3.10.0 中加入:将 /ajax-api/ 纳入受保护路径枚举,并把 allow_origins 收紧为配置的允许列表加 localhost 正则。2下文的防御建议面向的是本地 AI 代理这一整类问题,而不只是 MLflow。
要点
| 角色 | 行动 |
|---|---|
| 运行 MLflow 的 AI/ML 工程师 | 升级到 MLflow 3.10.0;审计其他监听回环地址的本地 AI 代理 UI |
| 安全团队 | 将“本地 LLM 代理回环服务”加入威胁建模模板;在这一类系统中,一个 CORS 缺口就等于 RCE |
| 构建本地代理 UI 的工具作者 | 统一枚举每个 API 路径前缀;即使服务器只绑定 localhost,也禁止 allow_origins=["*"];破坏性端点应要求新的用户手势,而不只是检查来源 |
| 工程负责人 | 为团队的 AI 编码代理加沙箱,并把其命令执行能力视为真正的影响范围 |
攻击链是什么样的
这条链路只有 5 步。每一步使用的都是被授权的能力,问题出在组合之后。
| 步骤 | 动作 | 攻击者控制什么 |
|---|---|---|
| 1 | 用户在本地运行 mlflow ui;Assistant 监听回环地址(默认 127.0.0.1) |
暂无(本地服务) |
| 2 | 用户在 MLflow 运行期间,在任意标签页中打开 evil.com |
页面内容 |
| 3 | evil.com 发起 fetch('http://127.0.0.1:5000/ajax-api/3.0/mlflow/assistant/config', { method: 'PUT', credentials: 'include', body: ... }) |
跨源请求 |
| 4 | 由于 /ajax-api/ 未出现在 CORS 阻断路径枚举中,MLflow 接受请求;Assistant 配置被切换到完全访问模式 |
Assistant 的执行环境配置 |
| 5 | evil.com 通过 Assistant 的聊天端点提交命令载荷;MLflow 在绕过权限检查的情况下调用本地 Claude Code CLI;命令以用户身份运行 |
任意代码执行 |
攻击者不需要在受害者机器上安装任何东西。只要受害者在 mlflow ui 运行期间加载一个包含载荷的标签页即可。由于旧版 CORSMiddleware 设置了 allow_origins=["*"],且路径级 CORS 阻断中间件没有覆盖 /ajax-api/ 前缀,攻击可以来自任意来源。2
Claude Code 集成是权限放大器。MLflow 的 ClaudeCodeProvider 会通过 shutil.which("claude") 查找本地 claude CLI,找不到时提示用户用 npm install -g @anthropic-ai/claude-code 安装。当 Assistant 的 permissions.full_access 配置标志开启时,提供程序会在调用 CLI 时追加 --permission-mode bypassPermissions。这是 Claude Code 中抑制交互式权限提示、允许工具调用无需用户确认即可执行的模式。3代理原本被授权拥有的能力(Shell、文件写入、HTTP)会变成带自然语言 UX 的远程 Shell,而这正是攻击者想要的。
根因是路径枚举不完整
读一下修复提交,漏洞就很清楚了。MLflow 的 security_utils.py 里有一个常量:
API_PATH_PREFIX = "/api/"
以及一个检查:
def is_api_endpoint(path: str) -> bool:
return path.startswith(API_PATH_PREFIX) and path not in TEST_ENDPOINTS
CORS 阻断中间件调用 is_api_endpoint() 来判断是否应用来源验证。任何不以 /api/ 开头的路径都会被当作非 API 路径放行。Assistant 为其面向浏览器的端点引入了 /ajax-api/。但没有人更新 is_api_endpoint(),把这个新前缀纳入检查。
修复在 security_utils.py 中只改变了一行行为:2
API_PATH_PREFIX = "/api/"
AJAX_API_PATH_PREFIX = "/ajax-api/"
def is_api_endpoint(path: str) -> bool:
return (
path.startswith(API_PATH_PREFIX) or path.startswith(AJAX_API_PATH_PREFIX)
) and path not in TEST_ENDPOINTS
同时,fastapi_security.py 也做了配套修复:把旧版 CORSMiddleware 中的 allow_origins=["*"] 替换为配置的允许列表加 localhost 正则。两个修改合起来,security_utils.py 中新增 5 行、删除 2 行,fastapi_security.py 中新增 5 行、删除 1 行。2
这就是整个漏洞。CVE-2026-2611 之所以发生,是因为一个新的端点前缀上线时,没人更新那个负责枚举哪些路径需要 CORS 保护的单一函数。攻击面只是一个被遗忘的配置常量。
为什么这种模式会反复出现
这个漏洞模式比 MLflow 更早,也比 AI 代理更早。只要存在路径枚举,Web 框架就长期存在路径枚举错误。变化的是后果。过去,本地 Web UI 漏枚举一个端点,通常只是泄露一些配置数据,或者触发拒绝服务。现在,一个运行具备 Shell 访问能力的 AI 编码代理的本地 Web UI,只要漏枚举一个端点,就可能暴露整台开发机器。
本地 AI 代理 UI 正在成为一个快速增长的类别。任何把 LLM 代理包装进由 127.0.0.1 提供服务的浏览器 UI 中的工具,都属于这个范围:带聊天面板的实验追踪器(MLflow 这种形态)、通过 localhost API 驱动代理的 IDE 插件、扩展通过 HTTP 通信的开发模式服务器、本地模型运行器的 Web 控制台。它们都绑定回环地址。它们都向浏览器标签页暴露变更类端点。它们都运行一个可以调用 Shell、写文件、推送到 git 的代理。它们距离 CVE-2026-2611 都只差一个路径枚举漏洞。
这一类问题背后的共同假设是:“浏览器的同源策略会保护回环服务器,因为合法请求也来自 127.0.0.1。”只要任何跨源关口(CORS、Host 头验证、Origin 头验证、Fetch Metadata 头)漏掉一个端点前缀,这个假设立刻失效。当服务器明确接受请求时,浏览器不会替服务器做保护。
我在 3 月写过的静默外传攻击面,是同一个信任假设在另一个方向上的破裂。静默外传把出口藏在代理被授权的工具里。回环地址误分类则把入口藏在浏览器被授权的网络调用里。二者都归结为同一点:只要任一边界检查失效,LLM 代理的授权能力就会变成攻击原语。
这一漏洞类别真正长什么样
这个漏洞类别有 4 个组成条件。四者同时成立时,服务就可被利用:
- 本地 Web 服务器绑定到回环地址或
0.0.0.0。更常见的是只绑定回环地址。保护绑定 localhost 的服务不被跨源标签页访问的,是浏览器的 CORS、Host 和 Origin 检查,而不是网络绑定本身。 - 变更类端点无需用户手势即可访问。配置变更、命令提交、文件写入。只要来自任意来源的
fetch()可以改变状态,服务就可达。 - 存在本地用户拥有、网页不应拥有的能力。读取主目录、运行 Shell 命令、使用已保存凭据调用云端 API、修改源代码文件。
- 缺失一个边界检查。被遗忘的路径前缀、通配符
allow_origins、缺失 Host 头验证、缺失 Origin 验证、缺失 Sec-Fetch-Site 强制检查。
CVE-2026-2611 四项全中。修复只是在 MLflow 中专门关闭了 /ajax-api/ 前缀对应的第 4 项。第 1 到第 3 项仍然存在于其他每一个本地 AI 代理工具里。这个类别里的下一个 CVE,看起来会一模一样,只是包名不同。
如何防御本地 AI 代理回环漏洞类别
标准 CORS 建议(把 allow_origins 设为明确列表,只在需要时启用凭据)是必要的,但远远不够。这一类问题需要更深的控制。
在一个地方枚举路径前缀,并写一个测试,确保新前缀不会在未受保护的情况下出现。MLflow 的漏洞之所以进入代码,是因为团队在 /api/ 上线时更新了 is_api_endpoint(),却在 /ajax-api/ 上线时忘了更新。一个测试只需遍历 FastAPI 应用上注册的每条路由,询问 is_api_endpoint() 该路由是否受保护,并在遇到任何未识别路径前缀时失败,就能在 PR 阶段抓住这个问题。这个检查大约 20 行测试代码即可完成。
验证 Origin 和 Host 头,而不只是依赖 CORS。CORS 保护的是浏览器读取服务器响应,不保护服务器不接受请求。本地 AI 代理服务应拒绝 Origin 头不在显式允许列表中的请求,也应拒绝 Host 头不是 127.0.0.1 或 localhost 的请求(后者用于防御 DNS rebinding 攻击)。这两项检查都应默认失败关闭。
对破坏性端点强制要求 Sec-Fetch-Site: same-origin。浏览器在任何跨源 fetch() 上都会发送 Sec-Fetch-Site: cross-site,包括从 evil.com 发往 127.0.0.1 的请求。Fetch Metadata Request Headers 规范定义了该头的取值,Chrome、Firefox、Safari 和 Edge 都会发送它。4中间件可以拒绝所有发往变更类端点且不是 Sec-Fetch-Site: same-origin 的请求。这提供了额外一层防线,而且不依赖于记住每一个路径前缀。
为 AI 代理的执行能力加沙箱。Claude Code、Cursor、Aider 和 Continue 默认都以用户完整权限运行,因为这样 UX 最省事。按工作区划分能力沙箱(Linux 上使用带明确挂载规则的 bwrap;以 .app 包发布的工具使用 macOS 应用沙箱;跨平台场景按工作区使用容器)可以在不牺牲核心工作流的情况下降低影响范围。这个缓解措施会带来摩擦。摩擦本来就是重点。
在审查前,把代理提交视为不可信。分支保护、强制评审、签名提交。“AI 代理拥有提交权限”这个模型,在攻击者攻陷代理本地环境的那一刻就会失效。我在 AI 代码审查需要异议中讨论过的评审者异议,在这里同样适用。如果 AI 代理和人类评审者总是一致,那么人类评审者只是装饰。
这个类别的下一个 CVE 可能是什么样
真正有意思的问题不是下一个形如 CVE-2026-2611 的漏洞会不会出现,而是哪一个包会先发布它。候选对象遍布本地 AI 工具生态:把代理嵌入 UI 的实验追踪器、通过 HTTP 与本地代理后端通信的 IDE 插件、会话期间任何工具打开的开发模式服务器、扩展用来驱动代理 UI 的 localhost API。这些工具目前还没有公开过这种形态的 CVE。但其中有不少代码路径都会绑定回环地址,并把浏览器当作信任关口来接受跨源请求。
这个类别比 CVE-2026-2611 更大,威胁模型已经改变。2026 年的开发者机器上,运行着可以调用 Shell、写文件、推送提交并调用云端 API 的 AI 代理。驱动这些代理的本地 Web UI,会通过自身 HTTP 服务器执行的信任边界继承代理权限。2020 年 Web 框架上的 CORS 配置错误,可能只是泄露一些配置数据。2026 年本地 AI 代理 UI 上的 CORS 配置错误,会暴露整台开发机器,以及生产部署能力。
修复不是一个补丁,而是一组控制:带测试的显式路径枚举、纵深防御式头验证、能力沙箱,以及对代理生成提交的审查纪律。只做其中一项、不做其余几项的工具,只是在用另一个前缀发布 CVE-2026-2611 的继任者。
FAQ
什么是 CVE-2026-2611?
CVE-2026-2611 是 National Vulnerability Database 为 MLflow 3.9.0 Assistant 功能中的来源验证不当漏洞分配的标识符。该漏洞允许任意网页向 Assistant 的本地 /ajax-api 端点发起跨源请求,修改其配置以启用完全访问模式,然后通过内置的 Claude Code CLI 集成执行任意命令。CVSS 3.0 基础评分为 9.6(CRITICAL)。该问题已通过 PR #20832“Block CORS for ajax paths”在 MLflow 3.10.0 中修复。12
根因是什么?
MLflow 的 is_api_endpoint() 检查只枚举了 /api/ 路径前缀。当 Assistant 功能引入 /ajax-api/ 端点时,没有人更新这个枚举。于是 CORS 阻断中间件在没有任何来源检查的情况下放行了发往 /ajax-api/ 的跨源请求。第二个弱点进一步放大了问题:旧版 CORSMiddleware 允许 allow_origins=["*"],因此即使是未被阻断的路径,也会向任意来源回显宽松的 CORS 头。修复是在路径检查中加入 AJAX_API_PATH_PREFIX = "/ajax-api/",并用配置的允许列表加 localhost 正则替换 allow_origins=["*"]。2
如何判断我的 MLflow 安装是否受影响?
运行 pip show mlflow | grep Version。根据 NVD 的 CPE 范围,MLflow 版本 >=3.9.0,<3.10.0 受影响。MLflow 3.10.0(2026年2月20日发布)包含修复。请使用 pip install --upgrade mlflow 升级,并重启所有正在运行的 mlflow ui 进程。
如果没有安装 Claude Code CLI,攻击还有效吗?
配置修改步骤仍然有效。任意代码执行步骤要求受害者的 PATH 中存在 Claude Code CLI,因为 MLflow 的 Assistant 会通过 shutil.which("claude") 调用它。如果没有 CLI,最坏情况会从 RCE 降级为信息泄露和任意配置篡改。对于已经配置 Claude Code 后端的用户(也就是文档化的 Assistant 设置路径),实际暴露面就是 RCE。
如何保护其他本地 AI 代理服务?
审计 4 个条件:回环地址或通配符绑定、变更类端点、代理级能力,以及任何可能存在缺口的单一边界检查。添加显式 Origin 允许列表,针对 127.0.0.1/localhost 做 Host 头验证(用于防御 DNS rebinding),并在破坏性端点上加入 Sec-Fetch-Site: same-origin 关口。使用 bwrap、macOS 应用沙箱或按工作区划分的容器,为代理执行能力加沙箱。在审查前,把代理生成的 git 提交视为不可信。
-
National Vulnerability Database。“CVE-2026-2611: MLflow Assistant improper origin validation。”发布于2026年5月19日。CVSS 3.0 基础评分 9.6(CRITICAL),向量
CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H,由 Huntr 发布。修复已在 MLflow 3.10.0 中提供。https://nvd.nist.gov/vuln/detail/CVE-2026-2611。原始报告:https://huntr.com/bounties/8462addd-b464-4a84-b6a2-5529604e6e5a。 ↩↩↩ -
MLflow。“Block CORS for ajax paths (#20832)。”由 Tomu Hirata 于2026年2月16日合并。变更文件:
mlflow/server/fastapi_security.py(+5/-1)、mlflow/server/security_utils.py(+5/-2),另含测试更新。该 diff 将is_api_endpoint()的单前缀检查替换为双前缀检查,并把旧版CORSMiddleware的allow_origins从["*"]收紧为配置的允许列表加 localhost 正则。https://github.com/mlflow/mlflow/commit/8f9c8a53af90842944101eb8b7d60706822c81bc。 ↩↩↩↩↩↩ -
MLflow 3.10.0 源码。
mlflow/assistant/providers/claude_code.py:shutil.which("claude")查找(第 268、280、355 行);安装提示"Install it with: npm install -g @anthropic-ai/claude-code"(第 286 行);权限模式升级cmd.extend(["--permission-mode", "bypassPermissions"])由config.permissions.full_access控制(第 379–381 行)。mlflow/assistant/config.py:PermissionsConfig.full_access: bool = False默认值(第 15 行)。mlflow/server/assistant/api.py:路由前缀/ajax-api/3.0/mlflow/assistant(第 63 行);GET /config读取端点(第 261 行);PUT /config变更端点(第 276 行),跨源攻击的目标正是这个 PUT 端点,用于打开full_access。已根据 PyPI 上的 MLflow 3.10.0 sdist(mlflow-3.10.0.tar.gz)验证。 ↩ -
W3C。“Fetch Metadata Request Headers。”定义
Sec-Fetch-Site及其取值cross-site、same-origin、same-site、none。Chrome 76+、Firefox 90+、Safari 16.4+ 和 Edge 79+ 均已实现。https://www.w3.org/TR/fetch-metadata/。 ↩