flawopen.com/AI Security/mlflow-webhook-ssrf-cve-2026-64849
CVE-2026-64849: SSRF в вебхуках MLflow
CVE-2026-64849 — SSRF без аутентификации в доставке вебхуков MLflow (высокая, CVSS 3.1: 8.6). Проверка исходного URL не ограничивала адреса перенаправлений и адрес фактического соединения; тестовый endpoint мог возвращать тело ответа целевого сервера.[1]
Представьте сотрудника, который проверил адрес на посылке, но разрешил курьеру следовать указанию о пересылке без проверки нового адреса. MLflow проверял URL вебхука до подключения, однако перенаправление и повторный DNS-запрос могли направить запрос в другое место. Исправление проверяет адрес реально подключённого узла до передачи HTTP-данных.[1], [2]
Ключевые понятия и термины
SSRF- Сервер выполняет выбранный вызывающей стороной сетевой запрос, пересекая границу доверия своей сети.[1]
Тестовый endpoint вебхука- API MLflow отправляет тестовое событие настроенному вебхуку; затронутый сервер по умолчанию возвращал вызывающему тело ответа.[1]
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-918
- D3-OTF Фильтрация исходящего трафика — Описанное в руководстве ограничение исходящего сетевого трафика сокращает список адресов, доступных сервису или хосту.
- CISA KEV — добавлено 2026-08-19 — CVE-2026-64849
Пошаговый сценарий атаки
Доступный сервер MLflow принимает тест вебхука
В бюллетене описан стандартный Tracking Server без аутентификации. Плагины аутентификации и настройки развертывания меняют доступность; проверьте реальную конфигурацию. 19 августа 2026 года CISA добавило CVE в каталог Known Exploited Vulnerabilities. Это указывает на известную эксплуатацию, но не доказывает компрометацию конкретного развертывания.[1], [4], [5]
Исходный URL проходит проверку
_validate_webhook_url разрешает исходное имя и проверяет адреса, но не сохраняет проверенный адрес для последующего сокета.[1]
Перенаправление или повторное разрешение ведёт к другой цели
HTTP-клиент следует перенаправлениям, а соединение может заново разрешить имя. Endpoint теста возвращает тело ответа, поэтому SSRF позволяет читать ответ целиком.[1]
Исправление upstream проверяет подключённый узел
PR #24258 проверяет адрес peer сокета сразу после соединения и до TLS/HTTP; сессия также отключает прокси из окружения. CVE исправлена в MLflow 3.15.0; отдельные обходы через IPv6 переходы исправлены в 3.16.0. В уведомлении freeman-bb указан как автор исходного закрытого сообщения, а AUTHENSOR — как независимый исследователь.[1], [2], [3], [6]
Исходный код: Уязвимый vs Защищённый вариант
Фрагменты показывают границу между проверкой URL и запросом, а также upstream-проверку peer в момент подключения. Запросы эксплуатации и внутренние адреса опущены. Нормализация IPv6 в 3.16.0 — более позднее отдельное усиление защиты.[2]
# Фрагмент mlflow/webhooks/delivery.py до 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
# Фрагмент upstream-исправления с проверкой узла при подключении.
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)
Чек-лист по защите системы для инженеров
- ✓Для
CVE-2026-64849обновите MLflow 3.10.0–3.14.x как минимум до 3.15.0; предпочтительна актуальная поддерживаемая версия. При использовании IPv6-переходов разверните 3.16.0 или новее.[1], [3], [6] - ✓Проверьте доступность Tracking Server из недоверенных сетей, наличие необязательного плагина аутентификации и проверок авторизации. Сетевые ограничения дополняют обновление, но не заменяют его.[1]
- ✓Ограничьте исходящий трафик сервера необходимыми адресами и блокируйте loopback, link-local, частные сети и облачные метаданные. Повторно проверяйте каждое перенаправление и подключённый peer в исходящих запросах. Оставляйте
MLFLOW_WEBHOOK_ALLOW_PRIVATE_IPSотключённой в production; включайте её только при явной необходимости в изолированной среде разработки.[2], [7] - ✓Добавьте регрессионные тесты CI: прямые непубличные адреса, цели перенаправлений, DNS rebinding, HTTP/HTTPS-пулы и прокси-маршруты должны блокироваться до отправки данных запроса; исключение для частных IP проверяйте отдельно.[2], [7]