19 października 2026 VulnCheck opublikował CVE-2026-53995 dla MLflow: serwer śledzenia przyjmuje od klienta identyfikator trace’a, a backend plikowy (FileStore) skleja z niego ścieżkę katalogu bez żadnej walidacji. Efekt: pliki trace’a lądują poza katalogiem eksperymentu, tam, gdzie proces MLflow ma prawo zapisu. Ocena CVSS 4.0 8.8, CVSS 3.1 8.2, bez uwierzytelnienia, publiczny PoC od autora zgłoszenia. Ciekawe są tu dwie rzeczy. Po pierwsze, numer wersji nie wystarcza do oceny, bo o podatności decyduje backend, a backend w MLflow 3.7+ zależy od tego, co leży w katalogu roboczym. Po drugie, w chwili pisania nie ma wydanej wersji z poprawką: PR jest otwarty, a gotowy już wpis 3.17.2 w changelogu tej poprawki nie zawiera.
mlflow server), backend FileStore.AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N, CVSS 3.1 8.2 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L. SSVC od CISA ADP: Exploitation poc, Automatable yes, Technical Impact partial.0, defaultStatus: unknown).Co dokładnie jest podatne
Endpoint POST /api/3.0/mlflow/traces (wiadomość StartTraceV3) służy klientom, czyli SDK, agentom i notebookom, do rejestrowania nowego trace’a. Klient wysyła cały obiekt TraceInfo, łącznie z identyfikatorem trace’a. W kodzie z tagu v3.17.1 handler _start_trace_v3() przekazuje ten obiekt do backendu bez sprawdzania identyfikatora. FileStore.start_trace() robi z nim to, co z każdą nazwą katalogu: dokleja go do ścieżki <eksperyment>/traces/, tworzy katalog i zapisuje w nim trace_info.yaml, metadane żądania i tagi. Jeśli identyfikator zawiera sekwencje przejścia do katalogu nadrzędnego, katalog i pliki powstają gdzie indziej. Ładunku nie przytaczam. Mechanizm jest klasyczny, a dla oceny ryzyka liczy się co innego.
Granice skutku, które warto zapisać przy findingu:
- Co jest zapisywane: pliki o stałych nazwach i strukturze MLflow (YAML z
TraceInfo, metadane, tagi), z treścią częściowo kontrolowaną przez atakującego. Autor PoC pisze wprost, że nie wykazał dowolnej treści plików, wykonania kodu ani wpływu na poufność. StądVC:NiTechnical Impact: partial. - Gdzie: wszędzie, gdzie proces MLflow może tworzyć katalogi. Proces uruchomiony jako root w kontenerze to inny przypadek niż dedykowany użytkownik z zapisem tylko do
mlruns. - Co może się popsuć: moja ocena, nie ustalenie źródeł. Najbliższy realny skutek to podrzucenie lub nadpisanie danych w strukturze innych eksperymentów tego samego store’u i zaśmiecanie systemu plików. Czy da się z tego zrobić coś więcej, np. zapis w miejscu czytanym przez inny komponent: niepotwierdzone, nikt tego publicznie nie pokazał.
PoC zgłaszającego, według opisu w README, wysyła żądanie przez Flask test client na wersji 3.17.1, czyli bez działającego serwera sieciowego. Autor sam zaznacza, że nie testował ekspozycji sieciowej ani konfiguracji uwierzytelnienia. Do oceny reachability w Twoim środowisku PoC więc nic nie wnosi, ale potwierdza sam mechanizm.
Wersja naprawiona: na dziś jej nie ma
To jest najważniejszy fakt dla procesu remediacji, więc sprawdziłem wszystkie źródła po kolei.
| Źródło | Co mówi o poprawce (stan 20.10.2026, ok. 12:00 UTC) |
|---|---|
| NVD (rekord CNA VulnCheck) | Zakres 0 do <= 3.17.1, defaultStatus: unknown. Brak wersji naprawionej, brak CPE. Status „Received”. |
| Advisory VulnCheck | Wersji naprawionej nie podaje. PR #26842 opisuje jako „proposed pull request”. |
| PR #26842 | Otwarty, niezmergowany. Autor: zgłaszający (etykieta community). Etykieta v3.17.2, w opisie zaznaczone „critical, next patch release”. Brak recenzji. Bot zgłosił nieudany test DCO (brak sign-off). |
| CHANGELOG.md na gałęzi master | Jest już wpis 3.17.2 (2026-10-20), ale wymienia tylko #26543, #26714 i #26713. #26842 w nim nie ma. |
| PyPI i GitHub Releases | Najnowsza wersja to 3.17.1 (13.10.2026). Strona 3.17.2 w PyPI zwraca 404. |
| Security advisories mlflow/mlflow | Brak advisory GHSA dla tej podatności. |
Wniosek: w chwili pisania wersja MLflow z poprawką CVE-2026-53995 nie jest potwierdzona. Etykieta v3.17.2 na PR mówi tylko, do jakiej wersji poprawka miałaby trafić. Wpis 3.17.2 w changelogu jej nie zawiera, a samo 3.17.2 nie jest jeszcze opublikowane. Możliwe, że PR wejdzie do 3.17.2 przed wydaniem, do 3.17.3 albo w innej formie, bo maintainerzy go jeszcze nie recenzowali. Skaner, który oznaczy „3.17.2 = fixed” na podstawie etykiety albo założenia „następny patch”, zamknie finding bez dowodu. Zasada jest ta sama co przy Dokploy: publiczny exploit wyprzedza dane o remediacji i trzeba to zapisać, a nie zgadywać.
Warto też wiedzieć, co PR zmienia, bo to wyznacza oczekiwania wobec przyszłego fixu. Według opisu: walidacja identyfikatorów jako bezpiecznych nazw katalogów (z zachowaniem kropek, spacji, Unicode), sprawdzanie granic po rozwiązaniu ścieżki przy tworzeniu, odczycie, wyszukiwaniu, aktualizacji i usuwaniu trace’ów, ochrona przed ucieczką przez linki w systemie plików i odpowiedź HTTP 400 dla złego ID. Dwa zdania z opisu PR są istotne dla VM. Walidacja jest specyficzna dla FileStore, inne backendy zachowują swoje formaty ID. Autor przyznaje też, że kontrola nadal ma ograniczenie typu check/use race, więc katalog store’u musi być zapisywalny tylko przez zaufane procesy.
Backend decyduje, a backend nie wynika z wersji
Opis CVE mówi: FileStore był domyślny przed 3.7.0, a od 3.7.0 jest opcjonalny, bo domyślny stał się SQLite. Na tej podstawie łatwo napisać regułę „<3.7.0 = podatne, ≥3.7.0 = podatne tylko przy jawnym FileStore”. Ta reguła jest niepełna. Changelog 3.7.0 opisuje zmianę tak: SQLite jest domyślny „unless existing mlruns data is detected”. Pomoc mlflow server --help w 3.17.1 mówi to samo: domyślnie sqlite:///mlflow.db, z powrotem do ./mlruns, gdy istniejący store jest obecny. W praktyce serwer, który działał na 3.6 z FileStore i został zaktualizowany do 3.17.1 bez zmiany parametrów, nadal używa FileStore. Nikt niczego jawnie nie wybrał, a podatność jest osiągalna.
| Konfiguracja | Backend tracking | Czy ścieżka z CVE jest osiągalna |
|---|---|---|
Wersja < 3.7.0, mlflow server bez --backend-store-uri | FileStore (./mlruns) domyślnie | Tak, o ile serwer obsługuje StartTraceV3 (dolnej granicy nie potwierdziłem, patrz niżej) |
Wersja ≥ 3.7.0, nowa instalacja, bez --backend-store-uri | SQLite (sqlite:///mlflow.db) | Nie (według CVE i PR walidacja dotyczy tylko FileStore) |
Wersja ≥ 3.7.0 po aktualizacji, w katalogu roboczym jest istniejący ./mlruns | FileStore (fallback opisany w --help i w changelogu 3.7.0) | Tak, bez żadnej „specjalnej konfiguracji” |
Jawne --backend-store-uri ./ścieżka albo file:///…, także przez MLFLOW_BACKEND_STORE_URI | FileStore | Tak |
--backend-store-uri PostgreSQL, MySQL, SQLite, MSSQL | SqlAlchemyStore | Nie dla opisanego zapisu plików (zastrzeżenie w tekście) |
Zastrzeżenie do ostatniego wiersza. W SqlAlchemyStore trace ID też trafia do URI lokalizacji artefaktów trace’a, które jest zapisywane jako tag. CVE i PR tego nie obejmują. Czy ma to jakikolwiek skutek przy zapisie danych spanów do artifact store, nie badałem: niepotwierdzone. Nie przenoszę tego CVE na backendy SQL, ale nie zapisuję też „SQL = na pewno bezpieczne” jako faktu.
Druga niepewność dotyczy dolnej granicy. Rekord CNA podaje zakres od 0. W kodzie z tagu v3.0.0 nie znalazłem handlera StartTraceV3, więc endpoint z opisu pojawił się później. Od której wersji dokładnie, nie ustaliłem. Dla VM to ma mniejsze znaczenie, bo starsze wersje i tak mają inne, publicznie znane problemy, ale „affected od 0” to w tym rekordzie raczej uproszczenie niż ustalenie.
Reachability: serwer śledzenia, który „ma być otwarty”
PR:N w wektorze jest prawdziwe dla domyślnej aplikacji MLflow, bo ta nie ma uwierzytelnienia. Ale cała ścieżka od Internetu do start_trace() ma kilka warstw i każdą trzeba sprawdzić osobno.
| Warstwa | Domyślnie (kod 3.17.1) | Co zmienia PR:N z wektora |
|---|---|---|
| Bind | --host 127.0.0.1 | Z sieci nieosiągalne. W kontenerach i na serwerach zespołowych prawie zawsze ktoś ustawia 0.0.0.0. |
| Security middleware (uvicorn) | Włączony, „localhost-only” przy braku --allowed-hosts | Filtruje nagłówek Host i CORS. To nie jest uwierzytelnienie: kto ma dozwolony adres, ten wysyła żądanie. |
| Uwierzytelnienie | Brak. Aplikacja basic-auth tylko przez --app-name basic-auth | Z basic-auth handler StartTraceV3 wymaga prawa tworzenia w eksperymencie, więc realnie PR:L, nie PR:N. |
| Reverse proxy z SSO | Poza MLflow | Zamyka drogę z zewnątrz, ale nie od klientów, którzy i tak mają wysyłać trace’y (agenci, notebooki, CI). |
Problem z serwerami śledzenia ML polega na tym, że ich rolą jest przyjmowanie zapisów od wielu klientów. Zespół data science uruchamia mlflow server --host 0.0.0.0 w kontenerze albo na VM, żeby notebooki, joby treningowe i agenci GenAI mogli logować runy i trace’y. Często bez basic-auth, bo „to sieć wewnętrzna”. Taki serwer nie jest „aplikacją z panelem logowania”, tylko otwartym API zapisu. Ten sam wzorzec opisywałem przy platformach agentów AI z domyślnym docker-compose: domyślna konfiguracja deweloperska trafia do sieci bez zmian.
Kontekst threat state: CVE-2026-53995 nie ma w KEV, ale MLflow w KEV już jest. CVE-2026-64849, SSRF przez nieuwierzytelniony endpoint testowania webhooków (3.3.0 do przed 3.15.0, naprawione w 3.15.0), trafiło do katalogu 19.08.2026. To nie przenosi „exploited” na nowe CVE, ale mówi coś o atakujących: nieuwierzytelnione serwery MLflow są już celem, a SSVC CISA ocenia też nowe CVE jako automatable: yes. Lista advisories GHSA projektu z października 2026 dwa razy używa sformułowania „on unauthenticated servers”, co dobrze oddaje model zagrożenia tej klasy oprogramowania.
Jak to ocenić u siebie
- Inwentarz: znajdź procesy i kontenery
mlflow server(imlflow ui), wersję pakietumlfloww ich środowisku. Sama obecność pakietu w obrazie z notebookami to co innego niż działający serwer śledzenia. - Backend: sprawdź
--backend-store-uriw linii poleceń i zmiennąMLFLOW_BACKEND_STORE_URI. Jeśli żadne nie jest ustawione, sprawdź katalog roboczy procesu: istniejącymlrunsoznacza FileStore także na 3.7+, sammlflow.dboznacza SQLite. Ścieżka lokalna albofile://to FileStore. - Sieć: na jakim adresie słucha (
127.0.0.1czy0.0.0.0), czy port jest wystawiony z kontenera, czy jest za reverse proxy. Pamiętaj, że API jest dostępne pod prefiksami/api/i/ajax-api/, więc reguła na proxy blokująca jeden prefiks nie wystarczy. - Uwierzytelnienie: czy serwer działa z
--app-name basic-authalbo za SSO. Jeśli tak, w ocenie zmieńPR:NnaPR:L: atakujący potrzebuje konta z prawem tworzenia w jakimś eksperymencie. - Uprawnienia procesu: jako kto działa MLflow i gdzie może pisać. To wyznacza realny zasięg zapisu poza katalogiem.
Priorytet z tego wynika wprost: FileStore + nasłuch poza localhost + brak uwierzytelnienia to finding pilny, mimo braku KEV. FileStore za uwierzytelnieniem to wysoki, ale z niższym PR. Backend SQL to „not affected według dostępnych danych” z adnotacją o zastrzeżeniu wyżej, a nie zamknięcie bez śladu.
Co powinien zrobić VE
(1) Zarejestruj CVE-2026-53995 jako „fix not released”, z warunkiem zamknięcia „wydana wersja MLflow, której changelog albo advisory wymienia CVE-2026-53995 lub PR #26842”, a nie „wersja > 3.17.1”. (2) Dla serwerów na FileStore rozważ migrację do backendu bazodanowego. Od 3.10.0 MLflow ma komendę mlflow migrate-filestore, ale przed użyciem sprawdź dokumentację i zrób kopię store’u. Po migracji upewnij się, że stary ./mlruns nie zostaje w katalogu roboczym, bo serwer uruchomiony bez --backend-store-uri może do niego wrócić. Najpewniej podawać --backend-store-uri jawnie. (3) Do czasu poprawki ogranicz dostęp do serwera: nasłuch tylko tam, gdzie potrzebny, uwierzytelnienie (basic-auth lub proxy z SSO), a jeśli to możliwe, zapis trace’ów tylko z zaufanych sieci klientów. (4) Uruchamiaj proces z dedykowanym użytkownikiem bez zapisu poza katalogiem store’u, co ogranicza zasięg i tak samo pomaga na ograniczenie race, o którym pisze autor PR. (5) Przy serwerach, które były wystawione bez uwierzytelnienia na FileStore, przejrzyj katalogi store’u i ich otoczenie pod kątem katalogów trace’ów w nieoczekiwanych miejscach. Jakich dokładnie artefaktów szukać, źródła nie podają, więc to moja propozycja, nie procedura vendora. (6) Gdy pojawi się wydanie z poprawką, zweryfikuj je w changelogu i PyPI, a dopiero potem aktualizuj reguły skanera. Przy okazji sprawdź, czy serwery są już na ≥ 3.15.0 ze względu na CVE-2026-64849 z KEV.
Wzorzec warto znać szerzej: affectedness zależna od konfiguracji runtime, której nie widać w SBOM. Podobną logikę „tylko localhost to nie to samo co nieosiągalne” opisywałem przy OpenHop, a „dwa niewinne defaulty składające się w atak” przy Cua.
Powiązane na blogu
- DB-GPT, Langchain-Chatchat, Letta, piflow: 15 CVE dla agentów AI — problemem jest domyślny docker-compose
- Dokploy: publiczny exploit i brak jasnej patched version — kiedy vulnerability intelligence wyprzedza remediation data
- OpenHop CVE-2026-59179 — „tylko localhost” nie znaczy „nieosiągalne”
- Cua computer-server — dwa defaulty składają się w unauth RCE
- IBM Langflow 1.11.5 — rodziny podatności nie redukuj do jednego findingu
- GoAnywhere MFT CVE-2026-15913 — path traversal i permission-aware affectedness
Źródła
- NVD — CVE-2026-53995 (opis CNA, CVSS 4.0 i 3.1, CWE-22, SSVC CISA ADP, zakres wersji) — nvd.nist.gov
- VulnCheck — MLflow 3.17.1 Path Traversal via Trace ID API — vulncheck.com
- GitHub — mlflow/mlflow PR #26842, Fix FileStore trace path traversal (CVE-2026-53995), stan: otwarty — github.com
- GitHub — mlflow/mlflow CHANGELOG.md (wpis 3.17.2 z 2026-10-20 bez #26842; 3.7.0: SQLite domyślny, chyba że wykryto mlruns; 3.10.0: migrate-filestore) — github.com
- GitHub — mlflow/mlflow Releases (najnowsza v3.17.1) — github.com
- PyPI — mlflow (najnowsza 3.17.1) — pypi.org
- GitHub — mlflow/mlflow Security advisories — github.com
- GitHub — mlflow/mlflow v3.17.1, mlflow/store/tracking/file_store.py (FileStore.start_trace) — github.com
- GitHub — mlflow/mlflow v3.17.1, mlflow/cli/__init__.py (domyślny backend, security middleware) — github.com
- Repozytorium zgłaszającego z opisem i PoC (opisane w tekście, kod nie jest tu przytaczany) — github.com
- NVD — CVE-2026-64849, MLflow SSRF w testowaniu webhooków (w CISA KEV) — nvd.nist.gov
- MLflow — Tracking server, architektura i self-hosting — mlflow.org
- CISA Known Exploited Vulnerabilities Catalog (CVE-2026-53995 nieobecne w wersji 2026.10.16) — cisa.gov
Nota redakcyjna: stan informacji 20 października 2026, około 12:00 UTC, na podstawie rekordu NVD API 2.0 dla CVE-2026-53995 (status „Received”, CNA VulnCheck, CVSS 4.0 8.8 i 3.1 8.2, CWE-22, SSVC CISA ADP, zakres do 3.17.1 bez wersji naprawionej i bez CPE), advisory VulnCheck z 19.10.2026, metadanych PR #26842 z GitHub API (otwarty, niezmergowany, etykieta v3.17.2, bez recenzji, nieudany test DCO), pliku CHANGELOG.md z gałęzi master (wpis 3.17.2 z datą 2026-10-20 bez #26842), danych PyPI i GitHub Releases (najnowsza 3.17.1), listy advisories GHSA projektu, kodu źródłowego MLflow z tagów v3.17.1 i v3.0.0, opisu w repozytorium zgłaszającego oraz katalogu CISA KEV w wersji 2026.10.16. Kodu exploita nie publikuję. Nie potwierdziłem: wydanej wersji z poprawką (3.17.2 nie jest opublikowane, a jego wpis w changelogu nie zawiera #26842), czy PR zostanie zmergowany w obecnej formie i do której wersji, dolnej granicy zakresu podatnych wersji, wykorzystania w atakach, możliwości przejścia od zapisu plików do wykonania kodu, skutków trace ID w URI artefaktów w backendach SQL, zachowania oficjalnych obrazów Docker i usług zarządzanych ani skali ekspozycji serwerów MLflow w Internecie. Interpretacja skutków zapisu, tabela reachability, zmiana PR:N na PR:L przy basic-auth, priorytety i lista „Co powinien zrobić VE” to oceny autora, nie ustalenia MLflow ani VulnCheck.