FlawOpen

flawopen.com/AI Security/mlflow-webhook-ssrf-cve-2026-64849

● CVE-2026-64849 · CVSS 8.6 · Alta
Pesquisa · FlawOpen

CVE-2026-64849: SSRF em webhooks do MLflow

CVE-2026-64849 é uma falsificação de requisição do lado do servidor (SSRF) sem autenticação na entrega de webhooks do MLflow (alta, CVSS 3.1: 8.6). A validação da URL inicial não restringia os destinos de redirecionamento nem o endereço usado pela conexão real; o endpoint de teste podia devolver o corpo da resposta upstream.[1]

💡 Explicação em Linguagem Simples (ELI5)

Imagine uma recepção que confere o endereço escrito no pacote, mas deixa o entregador seguir uma instrução de encaminhamento sem verificar o novo destino. O MLflow verificava a URL do webhook antes da conexão, mas redirecionamentos e uma nova consulta DNS podiam levar a requisição a outro lugar. A correção verifica o endereço do peer realmente conectado antes de enviar dados HTTP.[1], [2]

Conceitos Centrais e Termos

SSRF
O servidor faz uma requisição de rede escolhida pelo solicitante e cruza a fronteira de confiança da rede.[1]
Endpoint de teste de webhook
API do MLflow que envia um evento de teste ao webhook configurado; em servidores padrão afetados, o corpo upstream era retornado ao solicitante.[1]
DNS rebinding / TOCTOU
O hostname resolve para um IP público durante a validação e para outro IP não público no momento da conexão.[1], [2]

Mapeamentos de frameworks

Fluxo de Ataque Passo a Paso

Passo 1

Um servidor MLflow acessível aceita o teste de webhook

O alerta descreve um Tracking Server padrão sem autenticação. Plugins de autenticação e controles de implantação podem alterar o alcance; confirme a configuração real. A CISA adicionou o CVE ao catálogo Known Exploited Vulnerabilities em 19 de agosto de 2026; a inclusão indica exploração conhecida, mas não comprova o comprometimento de uma implantação específica.[1], [4], [5]

Passo 2

A URL inicial passa pela validação

_validate_webhook_url resolve e verifica o hostname original, mas descarta o endereço verificado em vez de vinculá-lo ao socket posterior.[1]

Passo 3

Um redirecionamento ou nova resolução chega a outro destino

O cliente HTTP segue redirecionamentos e a conexão pode resolver o hostname novamente. A resposta do teste refletia o corpo upstream, permitindo SSRF de leitura completa.[1]

Passo 4

A correção upstream verifica o peer conectado

O PR #24258 valida o peer do socket logo após a conexão e antes dos dados TLS/HTTP; a sessão também evita o roteamento por proxy do ambiente. MLflow 3.15.0 corrige a CVE; falhas IPv6 distintas foram corrigidas em 3.16.0. O aviso credita freeman-bb como denunciante privado original e AUTHENSOR como pesquisador independente.[1], [2], [3], [6]

Código-Fonte: Vulnerável vs. Seguro

Os trechos mostram a fronteira entre validação da URL e a requisição original, além da verificação upstream do peer no momento da conexão. Não incluem requisições de exploração nem destinos internos. A normalização IPv6 do 3.16.0 é uma melhoria posterior e separada.[2]

IMPLEMENTAÇÃO VULNERÁVEL
# Trecho de mlflow/webhooks/delivery.py antes do 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
PATCH SEGURO E ROBUSTO
# Trecho da correção upstream que verifica o peer no momento da conexão.
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 Verificação de Segurança para Engenharia

Fontes

← AI Security Directory →