
Co znajdziesz w tym artykule?
- 1 Wprowadzenie do problemu / definicja luki
- 2 W skrócie
- 3 Kontekst / historia / powiązania
- 4 Analiza techniczna / szczegóły luki
- 5 Praktyczne konsekwencje / ryzyko
- 6 Rekomendacje operacyjne / co zrobić teraz
- 7 Różnice / porównania z innymi przypadkami
- 8 Podsumowanie / kluczowe wnioski
- 9 Źródła / bibliografia
Wprowadzenie do problemu / definicja luki
W Keras (biblioteka DL) odkryto lukę CVE-2025-12058 umożliwiającą arBITRALNE ODCZYTY PLIKÓW (LFI) z systemu hosta podczas ładowania zserializowanych modeli .keras oraz SSRF (wykonywanie żądań sieciowych po stronie serwera). Problem dotyczy warstwy preprocessingowej StringLookup/IntegerLookup, która dopuszczała podanie ścieżki do pliku lub URL jako źródła słownika („vocabulary”). Podczas deserializacji modelu Keras odczytywał tę ścieżkę, nawet przy aktywnym safe_mode=True. Luka została naprawiona w Keras 3.11.4.
W skrócie
- Identyfikator: CVE-2025-12058, CVSS 4.0: 5.9 (Medium) wg CNA (Google).
- Wektory ataku: LFI przez odczyt lokalnych plików (np. klucze SSH), SSRF przez zdalne handlery
tf.io.gfile(HTTP(S), GCS/HDFS). - Warunek: ofiara ładuje zewnętrzny, złośliwy model
.keras(supply chain / model zoo / współdzielone repo). - Dotknięte wersje: Keras ≤ 3.11.3; naprawa w 3.11.4.
- Naprawa: włączenie osadzania słownika w archiwum
.kerasoraz zablokowanie ładowania zewnętrznych słowników przysafe_mode=True.
Kontekst / historia / powiązania
Keras 3.x wprowadził ujednolicony interfejs (JAX/TensorFlow/PyTorch), a wraz z nim nowy format .keras do zapisu modeli. Od początku zakładano mechanizmy „bezpiecznego ładowania” (safe_mode), jednak w tym przypadku ochrona nie obejmowała konstrukcji warstw *Lookup z parametrem vocabulary wskazującym ścieżkę/URL. Problem zgłosił zespół Zscaler (ThreatLabz) — opisując realne scenariusze nadużyć oraz oś czasu ujawnienia. Poprawki trafiły do głównej gałęzi w październiku 2025 r., a wydanie naprawcze opublikowano jako 3.11.4.
Analiza techniczna / szczegóły luki
Gdzie leży błąd?
StringLookupiIntegerLookuppozwalały navocabulary=<ścieżka lub URL>.- Gdy model był wczytywany z
.keras, deserializacja rekonstruowała warstwę i odczytywała wskazaną ścieżkę/URL, włączając zawartość do stanu warstwy (np. dostępna przezget_vocabulary()), nie respektując w pełnisafe_mode=True.
Skutki techniczne
- LFI (Local File Inclusion/Read): atakujący może ukryć w modelu ścieżki typu
/home/user/.ssh/id_rsa; podczas ładowania Keras wczyta treść pliku do słownika warstwy. - SSRF: Keras używa
tf.io.gfile, które obsługuje zdalne systemy plików i protokoły (HTTP/HTTPS). Specjalnie przygotowany URL może spowodować żądania sieciowe (np. do IMDS 169.254.169.254 w chmurze).
Co zmieniła poprawka?
PR #21751 sprawił, że:
- Słowniki są osadzane bezpośrednio w archiwum
.keras, dzięki czemu ładowanie nie sięga po zewnętrzne ścieżki. - Przy
safe_mode=Truezabroniono ładowania zewnętrznych plików słownika — dopuszczalne źródło to wyłącznie zawartość archiwum. Zmiana jest częściowo „breaking”. Zmergowano 17 października 2025 r. i wydano jako Keras 3.11.4.
Praktyczne konsekwencje / ryzyko
- Eksfiltracja sekretów dewelopera/serwera: klucze SSH, tokeny w plikach konfiguracyjnych,
.env,~/.aws/credentialsmogą zostać „wciągnięte” do modelu i odczytane przez napastnika po jego ponownym pobraniu lub przez kontrolowany przez niego pipeline. - SSRF do usług wewnętrznych: dostęp do IMDS (IAM w chmurze), wewnętrznych API, brokerów metadanych — w konsekwencji przejęcie zasobów chmurowych lub CI/CD.
- Łańcuch dostaw modeli: ryzyko dotyczy repozytoriów modeli, notebooków, benchmarków, konkursów, gdzie użytkownicy chętnie ładują cudze artefakty.
Rekomendacje operacyjne / co zrobić teraz
1) Natychmiastowa aktualizacja
- Uaktualnij do Keras ≥ 3.11.4 w środowiskach, które w jakikolwiek sposób ładują cudze
.keras.# sprawdź wersję python - <<'PY' import keras, sys print("Keras:", keras.__version__) PY # aktualizacja (przykład dla pip) python -m pip install --upgrade "keras>=3.11.4"
2) Wymuś bezpieczne ładowanie i walidację konfiguracji
- Przed
load_model()otwórz archiwum.kerasi przejrzyj konfigurację warstw; odrzuć modele, w którychStringLookup/IntegerLookupmająvocabularyjako ścieżkę/URL.import json, zipfile, re, sys from urllib.parse import urlparse def is_path_or_url(v): if not isinstance(v, str): return False if re.match(r'^[a-zA-Z]:[\\/]|^/|^\./|\.\./', v): # Windows/Unix path return True try: u = urlparse(v) return u.scheme in {"http","https","gs","hdfs"} except: return False def keras_archive_has_external_vocab(keras_path): with zipfile.ZipFile(keras_path) as z: with z.open("config.json") as f: cfg = json.load(f) bad = [] def walk(d): if isinstance(d, dict): if d.get("class_name") in {"StringLookup","IntegerLookup"}: vocab = d.get("config",{}).get("vocabulary", None) if is_path_or_url(vocab): bad.append((d["class_name"], vocab)) for v in d.values(): walk(v) elif isinstance(d, list): for v in d: walk(v) walk(cfg) return bad path = sys.argv[1] offenders = keras_archive_has_external_vocab(path) if offenders: print("BLOCK:", offenders); sys.exit(1) else: print("OK")(Skrypt defensywny — nie ładuje modelu, wyłącznie statycznie analizuje archiwum.) - Ładuj modele zawsze z
safe_mode=True(po aktualizacji ma znaczenie):import keras model = keras.saving.load_model("model.keras", safe_mode=True)
3) Odseparuj IO modelu
- Uruchamiaj proces ładujący w sandboxie bez dostępu do sekretów (AppArmor/SELinux,
no-new-privileges,fs.protected_hardlinks, profile Seccomp). - Read-only root, odmontowane
/home, brak dostępu do~/.ssh, brak zmiennych środowiskowych z sekretami.
4) Zablokuj SSRF u źródła
- Egress deny z jobów ładujących modele (Kubernetes
NetworkPolicy, eBPF Cilium, VPC egress firewall). - Blokada IMDS:
- AWS: metadane v2 tylko z hop-limit=1, lub
--http-endpoint disabledna EC2; w EKS —AwsNode.kubeProxyEnabled=false+iptables/CNI do odcięcia169.254.169.254. - GCP/Azure analogicznie — polityki blokujące dostęp z podów.
(Ogranicza efekt SSRF opisany w analizie.)
- AWS: metadane v2 tylko z hop-limit=1, lub
5) Detekcja w pipeline’ach
- Skanuj artefakty
.kerastymczasowo przed dopuszczeniem do trenowania/inferencji (skrypt powyżej). - Alertuj na outbound z jobów ładowania modeli (np. reguły w eBPF/XDP, IDS w podsieci).
- DLP/Git scanning: reguły wykrywające ciągi typu
StringLookup+vocabularyz wartością zaczynającą się od/lubhttp.
6) Zarządzanie zaufaniem do modeli (MLSBOM)
- Wprowadzaj Model SBOM (pochodzenie, hash, autor, podpis).
- Wymagaj podpisu kryptograficznego artefaktów modelu (Cosign/Sigstore) i weryfikuj go w CI przed
load_model().
Różnice / porównania z innymi przypadkami
- Deserializacja nieufnych danych (CWE-502) bywa znana z pickle/joblib. Tu wektorem jest format
.kerasi specyficzna opcjavocabularyw warstwach*Lookup. Mechanizmsafe_modemiał ograniczyć ryzyko, ale nie obejmował ścieżek słowników — stąd podatność. - W odróżnieniu od klasycznych RCE przez deserializację, tutaj mamy LFI/SSRF, co i tak może prowadzić do pełnej kompromitacji (np. przejęcie IAM i lateral movement).
Podsumowanie / kluczowe wnioski
- Aktualizacja do Keras 3.11.4 jest najważniejsza.
- Nie ładuj niezweryfikowanych modeli
.keras— traktuj je jak binaria z internetu. - Waliduj konfigurację warstw
*Lookupprzed deserializacją; blokuj egress i IMDS w jobach ML. - Segmentacja i sandbox procesu ładującego + telemetria wyjść sieciowych minimalizują skutki ewentualnego SSRF.
- Wdrażaj łańcuch zaufania do modeli (podpisy, SBOM), jak w klasycznych łańcuchach dostaw.
Źródła / bibliografia
- SecurityWeek: ogłoszenie luki i informacja o wersji naprawczej 3.11.4. (SecurityWeek)
- Zscaler (ThreatLabz): analiza techniczna, scenariusze eksploatacji (LFI/SSRF), dotknięte wersje ≤3.11.3. (Zscaler)
- NVD: karta CVE-2025-12058, opis wektora (LFI/SSRF), CVSS 4.0 5.9 (CNA), powiązane CWE-502. (NVD)
- GitHub (keras-team), PR #21751: szczegóły implementacji poprawki (osadzanie słownika, restrykcja
safe_mode). (GitHub) - Dokumentacja Keras: kontekst działania
StringLookup. (keras.io)