flawopen.com/AI Security/mlflow-webhook-ssrf-cve-2026-64849
CVE-2026-64849 : SSRF des webhooks MLflow
CVE-2026-64849 est une falsification de requête côté serveur (SSRF) sans authentification dans la livraison des webhooks MLflow (élevée, CVSS 3.1 : 8.6). La validation de l’URL initiale ne contrôlait ni les cibles de redirection ni l’adresse de la connexion réelle ; l’endpoint de test pouvait renvoyer le corps de la réponse distante.[1]
Imaginez un accueil qui vérifie l’adresse d’un colis, puis laisse le livreur suivre une consigne de réexpédition sans contrôler la nouvelle destination. MLflow vérifiait l’URL du webhook avant la connexion, mais une redirection ou une seconde résolution DNS pouvait envoyer la requête ailleurs. Le correctif vérifie l’adresse du pair réellement connecté avant tout échange HTTP.[1], [2]
Concepts Clés et Termes
SSRF- Le serveur effectue une requête réseau choisie par l’appelant et franchit sa frontière de confiance réseau.[1]
Endpoint de test webhook- API MLflow envoyant un événement de test au webhook configuré ; sur les serveurs par défaut concernés, le corps distant était renvoyé à l’appelant.[1]
Correspondances de référentiels
- Référence CWE pour la faiblesse décrite dans ce guide : CWE-918
- D3-OTF Filtrage du trafic sortant — La restriction du trafic sortant décrite dans ce guide limite les destinations accessibles au service ou à l’hôte.
- CISA KEV — ajouté le 2026-08-19 — CVE-2026-64849
Déroulement de l'Attaque Étape par Étape
Un serveur MLflow joignable accepte un test webhook
L’avis décrit un Tracking Server par défaut sans authentification. Les plugins d’authentification et contrôles de déploiement changent l’exposition ; vérifiez la configuration réelle. La CISA a ajouté cette CVE à son catalogue Known Exploited Vulnerabilities le 19 août 2026 ; cette inscription signale une exploitation connue, sans établir la compromission d’un déploiement particulier.[1], [4], [5]
L’URL initiale passe la validation
_validate_webhook_url résout et vérifie le nom initial, mais jette l’adresse vérifiée au lieu de la lier au socket ultérieur.[1]
Une redirection ou nouvelle résolution atteint une autre cible
Le client HTTP suit les redirections et la connexion peut résoudre à nouveau le nom. La réponse de test reflétait le corps distant, ce qui permettait une SSRF en lecture complète.[1]
Le correctif upstream vérifie le pair connecté
La PR #24258 valide le pair du socket juste après la connexion, avant TLS/HTTP ; la session évite aussi les proxys d’environnement. MLflow 3.15.0 corrige la CVE ; des contournements IPv6 distincts sont corrigés en 3.16.0. L’avis crédite freeman-bb comme premier signalement privé et AUTHENSOR comme découvreur indépendant.[1], [2], [3], [6]
Code Source : Vulnérable vs Sécurisé
Ces extraits montrent la frontière entre validation d’URL et requête, puis le contrôle upstream du pair à la connexion. Ils omettent les requêtes d’exploitation et les cibles internes. La normalisation IPv6 en 3.16.0 est un durcissement ultérieur distinct.[2]
# Extrait de mlflow/webhooks/delivery.py avant la PR #24258.
_validate_webhook_url(webhook.url) # checks the initial URL only
return session.post(
webhook.url, data=payload_bytes, headers=headers, timeout=timeout
) # redirects follow by default; the validated IP is not pinned
# Extrait du correctif upstream qui vérifie le pair au moment de la connexion.
def _assert_public_peer(sock):
if _MLFLOW_WEBHOOK_ALLOW_PRIVATE_IPS.get():
return
try:
peer_ip = sock.getpeername()[0]
except OSError as exc:
sock.close()
raise SSRFProtectionError(
f"Could not determine webhook connection peer address: {exc}"
) from exc
try:
ip = ipaddress.ip_address(peer_ip)
except ValueError as exc:
sock.close()
raise SSRFProtectionError(
f"Webhook connection resolved to an invalid IP address: {peer_ip!r}"
) from exc
if not ip.is_global:
sock.close()
raise SSRFProtectionError(
f"Webhook connection blocked: {ip} is not a public IP address. "
"This may indicate a DNS rebinding attempt."
)
class _SSRFProtectedHTTPConnection(HTTPConnection):
def _new_conn(self):
sock = super()._new_conn()
_assert_public_peer(sock)
return sock
class _SSRFProtectedHTTPSConnection(HTTPSConnection):
def _new_conn(self):
sock = super()._new_conn()
_assert_public_peer(sock)
return sock
adapter = SSRFProtectedHTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.trust_env = False
session.mount("http://", adapter)
session.mount("https://", adapter)
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Mettez MLflow 3.10.0–3.14.x à niveau vers 3.15.0 au minimum pour
CVE-2026-64849; privilégiez une version actuelle prise en charge. Déployez 3.16.0 ou ultérieure si des mécanismes de transition IPv6 sont utilisés.[1], [3], [6] - ✓Vérifiez que le Tracking Server n’est pas exposé à des réseaux non fiables et que le plugin d’authentification optionnel et les contrôles d’autorisation sont actifs. Le filtrage réseau complète la mise à niveau sans la remplacer.[1]
- ✓Limitez les sorties réseau aux destinations nécessaires et bloquez loopback, link-local, réseaux privés et métadonnées cloud. Revalidez chaque redirection et le pair connecté dans le code de requêtes sortantes. Gardez
MLFLOW_WEBHOOK_ALLOW_PRIVATE_IPSdésactivée en production ; ne l’activez que dans un environnement de développement isolé si nécessaire.[2], [7] - ✓Ajoutez des tests de régression CI vérifiant que les adresses non publiques directes, les cibles de redirection, le DNS rebinding, les pools HTTP/HTTPS et les routes proxy sont bloqués avant l’envoi des données ; testez séparément l’exception pour les IP privées.[2], [7]