我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

MLflow 跟踪服务器上无需身份验证的 RCE(CVE-2026-116692)

CVE-2026-116692(CVSS 9.8)使任何能够访问默认配置的 MLflow 3.12.0-3.16.x 跟踪服务器的调用方,都可以上传 Python 代码并以服务器进程的身份运行;该漏洞已在 3.17.0 中修复。

执行摘要(TL;DR)

  • CVE-2026-116692 是 MLflow 自身的机器学习跟踪服务器上的远程代码执行(RCE)漏洞:任何能够访问该服务器的 HTTP 调用方都可以上传一个 Python 模块,并让服务器导入和运行它,在默认部署中无需任何凭据。GitHub 安全公告将其评为 Critical,CVSS v3.1 9.8(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)。
  • 受影响的版本为 MLflow 3.12.0 至 3.16.x,以及候选版本 3.12.0rc0。首个修复版本是 2026 年 10 月 7 日发布的 3.17.0。
  • 该漏洞是一条利用链:服务器对第三方 scorer 的反序列化会接受任何以允许列表中的包前缀开头的模块路径,而服务器自身的实验和制品端点又允许调用方把实验的制品位置指向这样的包,并把 Python 文件上传到它下面。scorer 是 MLflow 用于评估 LLM 应用和智能体输出的评估函数,第三方 scorer 则封装了 Ragas、DeepEval、TruLens 和 Phoenix 等库的评估指标。在官方 MLflow v3.14.0 镜像中,进程以 root 身份运行,因此代码也以 root 身份执行。
  • 目前尚无已知的在野利用,该 CVE 也未被列入 CISA 的 KEV 目录。安全公告附带了一个无害的概念验证(PoC)。
  • 请升级到 3.17.0 并重新创建所有服务器容器。如果您今天无法升级,请限制能够访问服务器的对象:基本身份验证无法阻止能够创建实验并上传制品的已认证用户。

我是否受影响?

如果您运行的 MLflow 跟踪服务器版本在 3.12.0 至 3.17.0(不含)之间,不应有权创建实验或上传制品的调用方可以通过 HTTP 访问它,并且其进程可以写入某个已安装的 scorer 包内部,您就会受到影响。 以下三个条件必须全部成立:

  • 您运行的 MLflow 跟踪服务器版本为 3.12.0(包括其候选版本)或更高,且低于 3.17.0。携带该漏洞的第三方 scorer 功能最早出现在 3.12.0 中。
  • 服务器的 HTTP 端点(实验、run、制品上传以及 scorer 调用路由)可以被本不应能够创建实验和上传制品的调用方访问。身份验证默认关闭,而 mlflow server 除非以其他 --host(例如 0.0.0.0)启动,否则绑定到 127.0.0.1;一旦它在回环地址之外监听,或位于向其转发请求的代理之后,这些端点就会暴露给任何能够访问其端口的人。启用基本身份验证后,暴露面则收窄为任何能够创建实验和 run 并上传制品的已认证主体。
  • 服务器进程可以向其已安装的某个 scorer 包下写入文件。安全公告指出,这一条件在官方 MLflow 镜像中成立,在由同一操作系统用户安装并运行的虚拟环境中也成立;只读的容器文件系统,或服务器进程无法写入的 scorer 包目录,可以阻断这条具体的放置链。
软件包 受影响版本 修复版本
mlflow(PyPI) 3.12.0rc0 至 3.16.x 3.17.0(2026 年 10 月 7 日)

就规模而言,PyPI 在截至 2026 年 10 月 28 日的一周内记录了 4,880,597 次 mlflow 下载;这一数字统计的是所有下载,而不仅仅是跟踪服务器。

请先查询正在运行的服务器,再与该部署中安装的软件包进行比较。仅凭软件包元数据,无法证明长期运行的进程已在升级后重启:

# Running server process (include your deployment authentication if required)
curl -fsS http://<server>:<port>/version

# Installed package: run inside the deployment with the server interpreter/user
docker exec <container> python -c "import mlflow; print(mlflow.__version__)"
python -c "import mlflow; print(mlflow.__version__)"

# On-disk package metadata only; this does not inspect the running process
python -m pip show mlflow | grep Version

# Dependency files (the advisory lists only the mlflow package, not mlflow-skinny)
grep -iE "^mlflow([^a-z0-9._-]|$)" requirements*.txt
grep -A10 '"mlflow"' Pipfile.lock | grep '"version"'

如何修复?

将服务器软件包升级到 3.17.0 并重新创建服务器容器,这样被替换的是正在运行的进程,而不仅仅是磁盘上的镜像:

pip install -U "mlflow>=3.17.0"

pip install -U 只会替换磁盘上的文件:升级前启动的服务器进程在重启之前仍会把存在漏洞的代码保留在内存中。请重启服务器进程(例如对其单元执行 systemctl restart,或使用你的进程管理工具的等效操作),然后通过 /version 端点向正在运行的服务器查询版本。返回值必须为 3.17.0 或更高版本:

curl -s http://<server>:<port>/version

对于容器化的服务器,请更改镜像标签、拉取镜像并重新创建容器;仅重启旧容器会保留旧代码。随后对新容器运行上文的 docker exec 版本检查:输出必须为 3.17.0 或更高版本。

同一版本还修复了两个相关问题:

  • CVE-2026-116690:无身份验证服务器上的任意文件读取,利用的是被标记为提示词的模型版本的来源路径(CVSS v3.1 7.5,影响 3.10.0 至 3.16.x);具体机制见下文 FAQ。
  • CVE-2026-116691:CrewAI 自动日志记录会把 LLM 提供商的 API 密钥记录到所记录的追踪 span 中(CVSS v3.1 6.5,影响 2.19.0 至 3.16.x)。

如果您今天无法升级,以下三项缓解措施可以缩小风险窗口,但每一项都有局限:

  • 限制跟踪服务器的网络可达性(绑定到回环地址、使用 VPN、带身份验证的入口或 IP 允许列表)。局限:这会缩小攻击面,但并未修复任何问题;边界内的任何调用方仍然可以利用该端点。
  • 启用 MLflow 的基本身份验证。局限:scorer 调用端点并没有受到针对具体资源的权限检查保护,因此拥有实验、run 和制品访问权限的已认证主体仍然构成威胁。
  • 使用只读的容器文件系统运行服务器,或让 scorer 包目录对服务器进程不可写。局限:这可以阻断这条具体的文件放置链,但并未修复底层的反序列化行为。

根本原因是什么?

scorer 反序列化器把字符串前缀当作来源边界,然后导入请求所指定的任何模块,而服务器自身的制品端点又允许调用方把模块放进这个边界之内。 MLflow 3.12.0 在开源版 MLflow 中引入了第三方 scorer 的注册和反序列化支持(提交 02eea260,拉取请求 #22634)。在 v3.14.0 的发布提交中,mlflow/genai/scorers/scorer_utils.py 定义了允许的根模块:

THIRD_PARTY_SCORER_ALLOWED_MODULES = frozenset({
    "mlflow.genai.scorers.ragas",
    "mlflow.genai.scorers.deepeval",
    "mlflow.genai.scorers.trulens",
    "mlflow.genai.scorers.phoenix",
})

在 mlflow/genai/scorers/base.py 中,反序列化器接受与某个根完全相同的路径或其任意子模块,然后使用请求可控的值执行三项操作:

module_path == m or module_path.startswith(m + ".")
...
module = importlib.import_module(module_path)
scorer_class = getattr(module, class_name, None)
scorer_instance = scorer_class(**init_kwargs)

包名前缀并不是来源边界。如果服务器进程可以在某个允许的包下写入文件,攻击者创建的子模块就能通过字符串检查,成为可导入的代码。

利用链的另一半是制品的目录布局。实验创建端点接受 artifact_location 参数,MLflow 会将每个 run 的制品目录推导为 <artifact_location>/<run_id>/artifacts/。在官方 MLflow v3.14.0 镜像中,调用方可以把实验的制品位置设置为 file:///usr/local/lib/python3.10/site-packages/mlflow/genai/scorers/trulens。随后,调用方通过 run 制品端点上传一个模块,该文件就会落在导入路径 mlflow.genai.scorers.trulens.<run_id>.artifacts.<module> 上,也就是允许列表的前缀之内。

最后,在 mlflow/server/handlers.py 中,_invoke_scorer_handler() 会在验证或解析所提供的追踪 ID 之前先反序列化并构造 scorer,因此攻击者控制的构造函数会率先运行。在身份验证被禁用的情况下(即复现所用的默认配置),不需要任何凭据。

如何被利用?

能够访问服务器的调用方利用 MLflow 自身的实验、run 和制品端点,在某个被允许的 scorer 包内写入一个 Python 模块,然后让 scorer 调用路由导入该模块并从中构造一个类。 安全公告所附的概念验证从头到尾串联了这些端点:

  1. 创建一个实验,使其 artifact_location 指向一个可写的、被允许的 scorer 包,例如官方镜像中的 file:///usr/local/lib/python3.10/site-packages/mlflow/genai/scorers/trulens。
  2. 创建一个 run,这会让 MLflow 分配 <artifact_location>/<run_id>/artifacts/。
  3. 通过 run 制品上传端点,将一个 Python 模块 audit_<token>.py 上传到该 run 的制品目录中。
  4. 提交一个序列化的第三方 scorer,其 module 路径解析到所上传的文件:mlflow.genai.scorers.trulens.<run_id>.artifacts.audit_<token>。
  5. 通过 POST /ajax-api/3.0/mlflow/scorer/invoke 触发导入和类的构造。

随后,攻击者控制的构造函数便会在 MLflow 服务器进程中执行。在概念验证(PoC)中,构造函数会在其自身上传的制品旁写入一个随机标记文件,PoC 随后通过 run 制品端点下载该文件并检查其是否完全一致,以此证明代码已被执行。之后出现的追踪处理失败和 HTTP 500 都发生在构造函数已经运行之后,因此这个 500 并不能否定利用的成功。

安全公告中的远程复现针对一台一次性的云主机运行了同一条利用链,该主机运行着按清单摘要固定的官方 v3.14.0 镜像,进程以 uid 0 运行,标记文件报告 whoami: root。

flowchart LR A["创建实验,将
artifact_location 设在
允许的 scorer 包内"] --> B["创建 run:
<pkg>/<run_id>/artifacts/"] B --> C["将 audit_<token>.py
作为 run 制品上传"] C --> D["序列化的 scorer 指定
mlflow.genai.scorers.trulens.
<run_id>.artifacts.audit_<token>"] D --> E["POST /scorer/invoke:
前缀检查通过,
模块尚未验证"] E --> F["import_module 和构造函数
在服务器进程中执行"] F --> G["代码以服务器权限运行
(官方镜像中
为 uid 0)"]

攻击者能做什么?

在复现所用的默认无身份验证部署中,任何能够访问跟踪服务器的网络调用方都能以 MLflow 进程的身份执行 Python 代码,无需凭据,也无需用户交互。安全公告明确说明了影响范围:对服务器进程可访问的所有数据、凭据、网络访问和文件系统位置,在机密性、完整性和可用性方面造成全面影响。官方镜像以 root 身份运行该进程,这提高了容器层面的影响;对主机的影响仍取决于容器运行时配置、挂载的路径、授予的 capability 以及套接字的暴露情况。

截至 2026 年 10 月 28 日,尚无公开的武器化漏洞利用代码。

修复为何有效?

修复提交 a732443e(“Match third-party scorer modules exactly instead of by package prefix”,拉取请求 #25906,于 2026 年 9 月 16 日合并)将前缀匹配替换为精确匹配:

-            if not any(
-                module_path == m or module_path.startswith(m + ".")
-                for m in THIRD_PARTY_SCORER_ALLOWED_MODULES
-            ):
+            if module_path not in THIRD_PARTY_SCORER_ALLOWED_MODULES:

允许列表本身也扩充为逐字列出每一个具体的 scorer 模块,例如 mlflow.genai.scorers.ragas.scorers.rag_metrics,因此合法的导入仍然可以正常工作。像 mlflow.genai.scorers.trulens.<run_id>.artifacts.audit_<token> 这样由调用方放置的子模块不再与列表中的任何条目匹配,制品放置链因此被切断。另外三道关卡封堵了请求可能触及的其余导入路径:

  • metric_name 必须是一个普通的 Python 标识符,因为 MLflow 的 Ragas 和 DeepEval scorer 包装器在解析未知的指标名称时,会将其拼接进 ragas.metrics.collections.<metric_name> 或 deepeval.metrics.<metric_name> 这样的导入路径中。
  • kwargs 不能覆盖已验证的 metric_name。
  • 导入之后,获取到的类必须是 Scorer 的子类,才会被实例化。

这些措施共同确保反序列化器只导入 MLflow 自身附带的模块,而这正是最初设计的本意。

如何检查我是否已被入侵?

已公开的材料描述的是概念验证,而不是攻击者的入侵指标,因此并不存在公开的指标列表。根据安全公告本身所描述的机制,受影响的部署可以检查这条利用链留下的痕迹:

  • 查找已安装 scorer 包中比 MLflow 自身的 __init__.py 更新的 .py 文件。请使用服务器的解释器运行这些命令,使 $MLFLOW_ROOT 指向服务器实际导入模块的路径:
# Files placed inside installed scorer packages (run with the server's interpreter)
MLFLOW_ROOT=$(python -c "import mlflow, os; print(os.path.dirname(mlflow.__file__))")
find "$MLFLOW_ROOT/genai/scorers/" -type f -name '*.py' -newer "$MLFLOW_ROOT/__init__.py"
  • 审计跟踪存储,查找 artifact_location 为 Python 安装目录内 file:// 路径的实验,尤其是位于 site-packages/mlflow/genai/scorers/ 下的路径;概念验证使用的正是这种模式。对于 SQL 后端存储,请查询 experiments 表(下面的 SQLite 示例同样适用于 psql 或 mysql);对于基于文件的 mlruns 存储,每个实验的 meta.yaml 中记录了其 artifact_location:
# Experiments whose artifact location points into the scorer packages
sqlite3 mlflow.db "SELECT experiment_id, name, artifact_location FROM experiments WHERE artifact_location LIKE '%/mlflow/genai/scorers%';"
grep -l 'mlflow/genai/scorers' mlruns/*/meta.yaml
  • 在服务器访问日志中搜索发往 /ajax-api/3.0/mlflow/scorer/invoke 的 POST 请求,其载荷中带有 third_party_scorer_data,且其中的 module 路径包含 .artifacts. 片段。该利用链生成的路径始终形如 mlflow.genai.scorers.<package>.<run_id>.artifacts.<module>,32 位十六进制的 run ID 紧挨在 .artifacts. 之前,而 MLflow 自带的模块都不包含这一片段。
  • 在服务器访问日志中搜索 .py 文件的上传。MLflow 的 run 制品上传路由 POST /ajax-api/2.0/mlflow/upload-artifact 通过查询参数 path 接收目标文件名,因此即使上传的文件本身已被删除,这项检查依然有效:
grep -E 'POST /ajax-api/2\.0/mlflow/upload-artifact\?[^ ]*path=[^ &]*\.py' access.log

即使日志审查结果干净,也只能视为较弱的保证,尤其是当服务器曾在受影响的版本上、在开放网络中以禁用身份验证的状态运行过任何一段时间时。每一步都经过 MLflow 自身的实验、run、制品和 scorer 端点,因此没有任何请求指向不寻常的端点,只有上述这类细节才能将这些请求与正常使用区分开来。能够持久留存的痕迹是带有 file:// artifact_location 的实验记录和存放所上传模块的 run 目录。在概念验证中,模块旁边还会有一个标记文件。以服务器进程身份运行的代码可以删除所有这些痕迹。

如果发现其中任何痕迹,应将该服务器视为已被入侵,而不只是原地升级。安全公告将服务器进程可访问的所有数据、凭据、网络访问和文件系统位置都列入影响范围,因此请隔离该服务器,轮换该进程能够读取的所有凭据,并从干净的 3.17.0 或更高版本镜像重新创建服务器。

时间线

  • 2026-07-12:安全公告中经过脱敏的远程复现输出将这一日期记录为服务器时间,且安全公告指出截至该日期尚未发现已修复的版本(GitHub 安全公告,概念验证记录)。
  • 2026-09-16:修复提交 a732443e 通过拉取请求 #25906 合入 master:精确的模块匹配,外加标识符、kwargs 和子类三道关卡(GitHub 提交)。
  • 2026-10-07:包含修复的 MLflow 3.17.0 发布(GitHub releases)。
  • 2026-10-09:MLflow 发布仓库安全公告 GHSA-26p8-2jq9-3vq9(08:20 UTC),将该问题评为 Critical,CVSS v3.1 9.8(GitHub 安全公告)。
  • 2026-10-27:GitHub Advisory Database 收录 GHSA-26p8-2jq9-3vq9(21:26 UTC)。
  • 2026-10-28:本文发布。

修复本身是在 7 月复现之后两个多月才出现的。对于自 7 月起就暴露在外、且没有在 10 月 7 日升级的受影响服务器来说,在 10 月 9 日安全公告发布之前,唯一的公开信号只有 9 月 16 日的修复提交和 3.17.0 发布说明中的一行条目,两者的标题都是“Match third-party scorer modules exactly instead of by package prefix (GHSA-26p8-2jq9-3vq9)”。依赖扫描工具的反应还要更晚:Dependabot 告警基于 GitHub Advisory Database,而该数据库在 10 月 27 日才收录这份公告,OSV 条目也是同一日期,因此依赖这些告警的补丁流程在公告发布后 18 天、修复版本发布后 20 天内都没有收到任何信号。

CVE-2026-116692 的补丁竞赛,从 7 月的复现到 10 月的安全公告
图 1:修复于 10 月 7 日随 3.17.0 发布,MLflow 在两天后的 10 月 9 日发布了安全公告。来源:GitHub 安全公告、提交和版本发布,核实于 2026 年 10 月 28 日

常见问题

我的 MLflow 客户端会让我面临风险吗,还是只有自托管的服务器才会?

只有服务器端。这条利用链需要跟踪服务器自身的 HTTP 端点:使用指定的 artifact_location 创建实验、创建 run、上传制品以及调用 scorer。如果您只是把 MLflow 作为客户端,连接到由他人运营并负责打补丁的服务器,那么您的客户端版本不会带来这一风险。

启用身份验证能阻止它吗?

它能阻止无需身份验证的利用方式,但已认证的利用方式依然存在。安全公告发现,scorer 调用端点被分配给 validate_gateway_proxy,该函数在不进行任何实验级或 scorer 级资源权限检查的情况下直接返回 true,因此任何能够创建实验、创建 run 并上传制品的已认证主体仍然可以触及构造函数。

既然只读文件系统可以阻断它,为什么 CVSS 评分是 9.8?

9.8 的向量(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)评估的是安全公告所复现的默认部署:可通过网络访问、无身份验证、无需用户交互,并对机密性、完整性和可用性造成全面影响。只读文件系统这一条件是一种加固措施,只是恰好切断了这条特定利用链中的一个环节,而不是该软件默认配置的属性。

3.17.0 中修复的相关问题本身是否值得打补丁?

CVE-2026-116690 是无身份验证服务器上的任意文件读取漏洞(CVSS v3.1 7.5,影响 3.10.0 及更高、低于 3.17.0 的版本):被标记为提示词的模型版本可以使用 ../../../tmp 这样不带协议方案的相对路径作为其来源,随后服务器会从该路径提供文件。它所波及的部署比 RCE 早两个次要版本。

CVE-2026-116691(CVSS v3.1 6.5,影响 2.19.0 及更高、低于 3.17.0 的版本)会在启用 CrewAI 自动日志记录时,把 LLM 提供商的 API 密钥记录到所记录的追踪 span 中,这对任何共享追踪数据的部署来说都是一个数据暴露问题。

只需升级一次到 3.17.0,即可同时解决这三个问题。

是否有公开的漏洞利用代码?

安全公告附带了一个概念验证,并且刻意做成无害的:它写入一个标记文件,再下载回来检查是否一致。它证明了代码执行,却没有公开可用作武器的代码。截至 2026 年 10 月 28 日,尚无已知的在野利用,该 CVE 也未被列入 KEV 目录。

使用 Ostorlab 发现暴露的 MLflow 服务器

运行 Ostorlab Web 和 API 扫描,覆盖您的各个环境:它能识别暴露在外的 MLflow 跟踪服务器端点及其运行的版本,让您可以确认每个部署都已升级到 3.17.0 或更高版本,并且没有任何无需身份验证的实例可以从互联网访问。

参考资料

  • GitHub 安全公告 GHSA-26p8-2jq9-3vq9,CVE-2026-116692,CVSS v3.1 9.8,于 2026 年 10 月 9 日发布,并于 2026 年 10 月 27 日收录于 GitHub Advisory Database,包含完整的技术分析和概念验证附件:https://github.com/mlflow/mlflow/security/advisories/GHSA-26p8-2jq9-3vq9(核实于 2026 年 10 月 28 日)
  • 修复提交 a732443e,“Match third-party scorer modules exactly instead of by package prefix”,2026 年 9 月 16 日:https://github.com/mlflow/mlflow/commit/a732443e695c3c677680c54320efc394c81654ab(于 2026 年 10 月 28 日完整阅读)
  • 引入该功能的变更 02eea260, "Enable third-party scorer registration in OSS MLflow (#22634)", PR #22634: https://github.com/mlflow/mlflow/pull/22634 (于 2026 年 10 月 28 日核查)
  • MLflow 3.17.0 版本发布,2026 年 10 月 7 日:https://github.com/mlflow/mlflow/releases/tag/v3.17.0
  • 相关安全公告:GHSA-m65p-9j7q-6w33(CVE-2026-116690)和 GHSA-j7qq-vr8c-xvw3(CVE-2026-116691):https://github.com/advisories/GHSA-m65p-9j7q-6w33, https://github.com/advisories/GHSA-j7qq-vr8c-xvw3(核实于 2026 年 10 月 28 日)
  • 下载规模,PyPI 每周下载量 4,880,597 次:https://pypistats.org/packages/mlflow(核实于 2026 年 10 月 28 日)
  • CISA 已知被利用漏洞(KEV)目录,确认 CVE-2026-116692 未被列入:https://www.cisa.gov/known-exploited-vulnerabilities-catalog(核实于 2026 年 10 月 28 日)