flawopen.com/AI Security/mlflow-webhook-ssrf-cve-2026-64849
CVE-2026-64849: SSRF en webhooks de MLflow
CVE-2026-64849 es una falsificación de solicitudes del lado del servidor (SSRF) sin autenticación en la entrega de webhooks de MLflow (alta, CVSS 3.1: 8.6). La comprobación de la URL inicial no limitaba los destinos de redirección ni la dirección usada por la conexión real; el endpoint de prueba podía devolver el cuerpo de la respuesta upstream.[1]
Imagina una recepción que comprueba la dirección de un paquete, pero deja que el repartidor siga una nota de reenvío sin verificar el nuevo destino. MLflow comprobaba la URL del webhook antes de conectarse, pero las redirecciones y una segunda consulta DNS podían enviar la solicitud a otro lugar. La corrección comprueba la dirección del peer conectado antes de enviar datos HTTP.[1], [2]
Conceptos Clave y Términos
SSRF- El servidor realiza una solicitud de red elegida por quien llama y cruza el límite de confianza de su red.[1]
Endpoint de prueba de webhook- API de MLflow que envía un evento de prueba al webhook configurado; en servidores predeterminados afectados, devolvía el cuerpo upstream.[1]
Mapeos de marcos
- Referencia CWE para la debilidad descrita en esta guía: CWE-918
- D3-OTF Filtrado del tráfico saliente — La restricción de red saliente de esta guía limita los destinos a los que puede acceder el servicio o el host.
- CISA KEV — añadido el 2026-08-19 — CVE-2026-64849
Flujo de Ataque Paso a Paso
Un servidor MLflow accesible acepta una prueba de webhook
El aviso describe un Tracking Server predeterminado sin autenticación. Los plugins de autenticación y los controles de despliegue pueden cambiar el acceso; comprueba la configuración real. CISA añadió el CVE a su catálogo Known Exploited Vulnerabilities el 19 de agosto de 2026; la inclusión indica explotación conocida, pero no demuestra que una implementación concreta haya sido comprometida.[1], [4], [5]
La URL inicial supera la validación
_validate_webhook_url resuelve y comprueba el hostname original, pero descarta la dirección comprobada en vez de asociarla al socket posterior.[1]
Una redirección o nueva resolución alcanza otro destino
El cliente HTTP sigue redirecciones y la conexión puede resolver de nuevo el hostname. La respuesta de prueba reflejaba el cuerpo upstream, lo que permitía SSRF de lectura completa.[1]
La corrección upstream verifica el peer conectado
PR #24258 valida el peer del socket justo después de conectar y antes de datos TLS/HTTP; la sesión también evita proxies del entorno. MLflow 3.15.0 corrige esta CVE; fallos IPv6 distintos se corrigieron en 3.16.0. El aviso atribuye el reporte privado original a freeman-bb y reconoce a AUTHENSOR como investigador independiente.[1], [2], [3], [6]
Código Fuente: Vulnerable vs. Seguro
Estos fragmentos muestran la frontera entre validar la URL y efectuar la petición, junto con la comprobación upstream del peer al conectar. Omiten solicitudes de explotación y destinos internos. La normalización IPv6 de 3.16.0 es una mejora posterior independiente.[2]
# Fragmento de mlflow/webhooks/delivery.py anterior al 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
# Fragmento de la corrección upstream que comprueba el peer al conectar.
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)
Lista de Verificación de Seguridad para Ingeniería
- ✓Actualiza MLflow 3.10.0–3.14.x a 3.15.0 como mínimo para
CVE-2026-64849; prefiere una versión compatible actual. Usa 3.16.0 o posterior cuando puedan usarse mecanismos de transición IPv6.[1], [3], [6] - ✓Comprueba que el Tracking Server no esté expuesto a redes no confiables y si están activos el plugin opcional de autenticación y la autorización. Restringir la red complementa la actualización, no la sustituye.[1]
- ✓Limita la salida de red del servidor a destinos necesarios y bloquea loopback, link-local, redes privadas y metadatos cloud. Revalida cada redirección y el peer conectado en el código de solicitudes salientes. Mantén
MLFLOW_WEBHOOK_ALLOW_PRIVATE_IPSdesactivada en producción; habilítala solo en entornos de desarrollo aislados cuando sea necesario.[2], [7] - ✓Añade pruebas de regresión en CI para comprobar que se bloquean las direcciones no públicas directas, los destinos de redirección, el DNS rebinding, los pools HTTP/HTTPS y las rutas por proxy antes de enviar datos; prueba por separado la excepción para IP privadas.[2], [7]