Strona główna / Blog / MLflow CVE-2026-53995: FileStore i trace ID

Blog · analiza

MLflow CVE-2026-53995: path traversal przez trace ID. To, czy jesteś podatny, zależy od backendu, a wersji z poprawką jeszcze nie ma

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.

Produkt: MLflow Tracking Server (mlflow server), backend FileStore.
CVE: CVE-2026-53995, CWE-22. CNA VulnCheck, w NVD od 19.10.2026 (22:17 UTC), status „Received”, bez CPE. Zgłosił Sai Teja Erukude, koordynacja przez VulnCheck.
Ocena CNA: CVSS 4.0 8.8 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.
Wersje podatne: do 3.17.1 włącznie (rekord CNA: od 0, defaultStatus: unknown).
Poprawka: PR #26842, otwarty i niezmergowany. Wydana wersja naprawiona: w chwili pisania niepotwierdzona.
Threat state: brak w CISA KEV (katalog 2026.10.16). Publiczny PoC w repozytorium zgłaszającego. O wykorzystaniu w atakach nie znalazłem informacji.

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ąd VC:N i Technical 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łoCo 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 VulnCheckWersji naprawionej nie podaje. PR #26842 opisuje jako „proposed pull request”.
PR #26842Otwarty, 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 masterJest już wpis 3.17.2 (2026-10-20), ale wymienia tylko #26543, #26714 i #26713. #26842 w nim nie ma.
PyPI i GitHub ReleasesNajnowsza wersja to 3.17.1 (13.10.2026). Strona 3.17.2 w PyPI zwraca 404.
Security advisories mlflow/mlflowBrak 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.

KonfiguracjaBackend trackingCzy ścieżka z CVE jest osiągalna
Wersja < 3.7.0, mlflow server bez --backend-store-uriFileStore (./mlruns) domyślnieTak, o ile serwer obsługuje StartTraceV3 (dolnej granicy nie potwierdziłem, patrz niżej)
Wersja ≥ 3.7.0, nowa instalacja, bez --backend-store-uriSQLite (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 ./mlrunsFileStore (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_URIFileStoreTak
--backend-store-uri PostgreSQL, MySQL, SQLite, MSSQLSqlAlchemyStoreNie 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.

WarstwaDomyślnie (kod 3.17.1)Co zmienia PR:N z wektora
Bind--host 127.0.0.1Z 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-hostsFiltruje nagłówek Host i CORS. To nie jest uwierzytelnienie: kto ma dozwolony adres, ten wysyła żądanie.
UwierzytelnienieBrak. Aplikacja basic-auth tylko przez --app-name basic-authZ basic-auth handler StartTraceV3 wymaga prawa tworzenia w eksperymencie, więc realnie PR:L, nie PR:N.
Reverse proxy z SSOPoza MLflowZamyka 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

  1. Inwentarz: znajdź procesy i kontenery mlflow server (i mlflow ui), wersję pakietu mlflow w ich środowisku. Sama obecność pakietu w obrazie z notebookami to co innego niż działający serwer śledzenia.
  2. Backend: sprawdź --backend-store-uri w linii poleceń i zmienną MLFLOW_BACKEND_STORE_URI. Jeśli żadne nie jest ustawione, sprawdź katalog roboczy procesu: istniejący mlruns oznacza FileStore także na 3.7+, sam mlflow.db oznacza SQLite. Ścieżka lokalna albo file:// to FileStore.
  3. Sieć: na jakim adresie słucha (127.0.0.1 czy 0.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.
  4. Uwierzytelnienie: czy serwer działa z --app-name basic-auth albo za SSO. Jeśli tak, w ocenie zmień PR:N na PR:L: atakujący potrzebuje konta z prawem tworzenia w jakimś eksperymencie.
  5. 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

Ź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.

Zapamiętaj jedno

CVE-2026-53995 to path traversal przez trace ID w FileStore MLflow do 3.17.1, osiągalny bez uwierzytelnienia na domyślnej aplikacji. Wersja nie wystarcza do oceny: FileStore jest domyślny przed 3.7.0, ale także na 3.7+ serwer wraca do istniejącego ./mlruns, więc sprawdź backend w runtime. Potem sprawdź nasłuch, uwierzytelnienie i uprawnienia procesu. Wersji z poprawką w chwili pisania nie ma: PR #26842 jest otwarty, a wpis 3.17.2 w changelogu go nie zawiera. Zapisz finding jako „fix not released” z warunkiem zamknięcia opartym na changelogu, a do tego czasu zamknij serwer za uwierzytelnieniem albo przenieś go na backend bazodanowy.