Przejdź do treści
INDEKS ANALIZ BREACHROAD / NOTA TECHNICZNA

Niebezpieczna deserializacja: od danych do RCE

Niebezpieczna deserializacja w Java, .NET i Pythonie: naucz się rozpoznawać granice zaufania, testować bezpiecznie i usuwać przyczynę RCE.

MATERIAŁ PUBLICZNY
AUTOR
/ Pentester (OSCP, PNPT)
PUBLIKACJA
9 maja 2026
CZAS CZYTANIA
17 min czytania
TEMAT
AppSec
Niebezpieczna deserializacja: od danych do RCE

Niebezpieczna deserializacja powstaje, gdy aplikacja odtwarza obiekt z danych, którym nie powinna ufać, a format lub biblioteka pozwalają wejściu wpływać na tworzony typ, graf obiektów albo wywoływane metody. Skutek nie ogranicza się do zdalnego wykonania kodu. Może obejmować odmowę usługi, zmianę pól bezpieczeństwa, obejście autoryzacji, odczyt plików i nieoczekiwane połączenia sieciowe.

Najkrótsza odpowiedź: podpisanie payloadu nie sprawia, że niebezpieczny deserializer staje się bezpieczny. Podpis potwierdza integralność i źródło, ale nie usuwa gadżetów, błędów zarządzania kluczem, niebezpiecznych danych już obecnych w magazynie ani ryzyka DoS. Najlepszą strategią jest rezygnacja z natywnej deserializacji obiektów na granicy zaufania, użycie prostego formatu danych, ścisłego schematu i jawnego mapowania do dozwolonych typów.

MITRE klasyfikuje problem jako CWE-502: Deserialization of Untrusted Data. OWASP utrzymuje praktyczny Deserialization Cheat Sheet, a Microsoft wprost ostrzega, że BinaryFormatter nie może zostać zabezpieczony do przetwarzania niezaufanych danych.

Serializacja danych a odtwarzanie zachowania

Serializacja zamienia stan na zapis, który można przechować lub przesłać. Bezpieczniejszy model traktuje wejście jak dane: liczby, tekst, listy i jawnie określone rekordy. Ryzykowny model próbuje odtworzyć dowolny graf obiektów wraz z informacją o klasach, referencjach i mechanizmach wywoływanych podczas rekonstrukcji.

To ważna różnica. JSON nie wykonuje kodu tylko dlatego, że zawiera pole type, ale framework może dodać automatyczne polymorphic binding i na podstawie tego pola utworzyć wskazaną klasę. XML jest formatem danych, lecz konkretna biblioteka może obsługiwać typy i wywołania, które zmieniają go w powierzchnię ataku. Bezpieczeństwo zależy więc od formatu, konfiguracji, biblioteki i późniejszego użycia obiektu.

WarstwaPytanie bezpieczeństwa
źródłoczy dane mogą zostać zmienione przez użytkownika, malware lub dostawcę?
formatczy wejście deklaruje typy, referencje lub kod inicjalizacyjny?
deserializerczy istnieje ścisła allowlista klas i limity grafu?
classpath/runtimeczy dostępne są klasy z użytecznymi skutkami ubocznymi?
logika po odtworzeniuczy aplikacja ufa polom uprawnień lub ścieżkom z obiektu?

Łańcuch klas, których metody lub hooki tworzą niepożądany efekt podczas rekonstrukcji, bywa nazywany gadget chain. Gadżet nie musi pochodzić z kodu podatnej funkcji; może znajdować się w bibliotece dostępnej w procesie. Dlatego sama filtracja kilku znanych klas jest krucha. Podobnie jak w przypadku prototype pollution w JavaScript, końcowy skutek zależy od połączenia źródła danych z dostępnym gadżetem.

Gdzie pojawia się granica zaufania

Najbardziej oczywiste źródła to ciasteczka, parametry formularza, body API i pliki przesyłane przez użytkownika. Równie ważne są mniej oczywiste ścieżki:

  • wiadomości z kolejki, do której może pisać inna usługa;
  • cache lub baza danych, które mogą zostać zmienione przez SQL injection albo przejęte konto;
  • pliki sesji i „save state” na stacji roboczej;
  • backupy i importy od partnerów;
  • artefakty CI/CD i checkpointy modeli AI;
  • komunikacja wewnętrzna po zmianie architektury z desktopowej na chmurową.

Microsoft w przewodniku bezpieczeństwa BinaryFormatter podkreśla, że dane uważane za wewnętrzne często przekraczają granicę zaufania, a infekcja jednej stacji może wykorzystać deserializację do ruchu bocznego. To argument za modelowaniem przepływu, a nie oznaczaniem źródeł etykietą „internal = trusted”.

Java: ObjectInputStream i filtry serializacji

Natywna serializacja Java przechowuje informacje potrzebne do odtworzenia obiektów. Punktem przeglądu jest ObjectInputStream.readObject() oraz frameworki, które wywołują go pośrednio. Sygnał w ruchu może stanowić nagłówek strumienia AC ED 00 05 lub jego reprezentacja Base64 zaczynająca się często od rO0, ale brak tych znaczników nie wyklucza innych formatów.

Oracle dokumentuje Java Serialization Filters, rozwinięte w JEP 290 i JEP 415. Filtry mogą ograniczać dozwolone klasy oraz rozmiar i złożoność grafu: głębokość, liczbę referencji czy rozmiar tablic. To wartościowa ochrona migracyjna, lecz powinna być kontekstowa i możliwie wąska. Globalna allowlista „wszystkich klas aplikacji” nadal może zawierać gadżety.

Najbezpieczniejszy kierunek to DTO w prostym formacie, jawne pola i brak możliwości wyboru dowolnej klasy przez nadawcę. Jeżeli kompatybilność wymaga starego formatu, izoluj parser, ogranicz klasy, rozmiar wejścia, czas oraz uprawnienia procesu.

.NET: dlaczego BinaryFormatter został usunięty

Microsoft opisuje BinaryFormatter.Deserialize jako niebezpieczny dla niezaufanego wejścia i wskazuje, że nie da się go uczynić bezpiecznym. Od .NET 9 implementacja wbudowana została usunięta i wywołanie domyślnie kończy się wyjątkiem; szczegóły znajdują się w przewodniku migracji BinaryFormatter.

Nie należy przenosić problemu do innego deserializera z nieograniczonym polimorfizmem. Kluczowe pytanie brzmi: czy dane mogą wskazać typ do utworzenia? Preferuj System.Text.Json z konkretnym typem DTO, ścisłą konfiguracją i jawnie zaprojektowaną obsługą polimorfizmu. Następnie mapuj DTO do obiektu domenowego po walidacji.

Podpis HMAC może chronić payload przed modyfikacją, ale tylko wtedy, gdy klucz jest tajny, rotowany, poprawnie używany i sprawdzany przed deserializacją. Nie chroni przed złośliwym payloadem podpisanym przez przejętą usługę ani przed historycznym obiektem, który stał się niebezpieczny po aktualizacji classpath.

Python: pickle jest protokołem wykonawczym

Moduł pickle nie jest formatem do wymiany niezaufanych danych. Jego model pozwala opisać sposób rekonstrukcji obiektów, w tym wywołania potrzebne do ich utworzenia. Dokumentacja i OWASP zalecają, aby nie używać pickle.load() ani pickle.loads() dla danych z niepewnego źródła.

Podobne ryzyko może wystąpić w yaml.load() z loaderem pozwalającym tworzyć obiekty Pythona, w jsonpickle lub narzędziach ML korzystających pod spodem z pickle. Bezpieczniejsza alternatywa to JSON lub ograniczony YAML safe loader, walidacja schematu i ręczne tworzenie obiektu.

To łączy klasyczny AppSec z łańcuchem dostaw AI. Checkpoint pobrany z repozytorium jest wejściem przekraczającym granicę zaufania. Bezpieczne wdrażanie modeli AI wymaga sprawdzenia formatu, pochodzenia, sumy i środowiska ładowania, nie tylko rozszerzenia pliku.

Jak testować bez uruchamiania niebezpiecznego łańcucha

Test deserializacji powinien minimalizować możliwość wykonania kodu. Publiczne narzędzia generujące gadget chains mogą powodować procesy, połączenia sieciowe lub modyfikacje plików. Nie uruchamiaj ich na produkcji tylko po to, by „udowodnić RCE”.

1. Znajdź sink

W code review wyszukaj API deserializujące oraz wrappery. Ustal format, konfigurację polimorfizmu, dozwolone typy i źródło danych. SAST, DAST i IAST dają różne perspektywy: SAST znajduje wywołanie, IAST może potwierdzić przepływ, a DAST pokazuje zachowanie na granicy.

2. Zidentyfikuj granicę zaufania

Narysuj pełną trasę payloadu: klient, gateway, kolejka, cache, baza, worker. Ustal, kto może modyfikować dane i czy podpis jest weryfikowany przed parserem. Zwróć uwagę na funkcje importu oraz migracji, które działają z wysokimi uprawnieniami.

3. Użyj kanarka bez efektu systemowego

Preferuj nieznany, nieszkodliwy typ albo kontrolowany obiekt testowy, który rejestruje próbę utworzenia w pamięci procesu. Błąd „class rejected” może potwierdzić filtr. Odpowiedź czasowa lub stack trace bywa wskazówką, ale nie wystarcza jako dowód wpływu.

4. Potwierdź ograniczenia

Sprawdź maksymalny rozmiar, głębokość grafu, liczbę obiektów, timeout i zachowanie przy nieznanym typie. Deserializacja może prowadzić do DoS bez żadnego gadżetu RCE. Testy powinny mieć twarde limity i monitoring pamięci/CPU.

5. Oceń classpath i uprawnienia

W środowisku laboratoryjnym sprawdź, czy dostępne są klasy o skutkach ubocznych oraz co może zrobić proces. Nawet jeśli istnieje łańcuch, kontener bez sekretów, zapisu i egressu ogranicza wpływ. Secure by Design wymaga ochrony także wtedy, gdy parser zawiedzie.

Wzorzec docelowej naprawy

  1. Zastąp natywny format obiektowy formatem danych bez wykonywalnej semantyki.
  2. Zdefiniuj wersjonowany, ścisły schemat z limitem rozmiaru i głębokości.
  3. Parsuj do prostego DTO bez pól uprawnień ustalanych przez klienta.
  4. Waliduj typy, zakresy, relacje i reguły biznesowe.
  5. Utwórz nowy obiekt domenowy jawnie, nie przez automatyczne odtworzenie grafu.
  6. Uruchamiaj parser z minimalnymi uprawnieniami, bez zbędnego egressu i sekretów.
  7. Dodaj telemetrię odrzuceń, ale nie loguj całych wrażliwych payloadów.
KontrolaCo ograniczaCzego nie gwarantuje
prosty format danychwykonywalną semantykę formatupoprawności logiki po parsowaniu
ścisły schematnieznane pola i typybraku błędów biznesowych
allowlista klasdostęp do wielu gadżetówbezpieczeństwa dozwolonych klas
limity grafuczęść ataków DoSbraku skutków ubocznych
podpis/HMACnieautoryzowaną modyfikacjębezpieczeństwa samego payloadu
sandboxzasięg skutkówusunięcia podatności

Migracja bez zatrzymania systemu

Najpierw zinwentaryzuj producentów i konsumentów starego formatu. Wprowadź nowy, wersjonowany format i okres dual-read, ale zapisuj już tylko nowy. Mierz użycie starej ścieżki, migruj dane spoczynkowe i usuń fallback w określonym terminie. Nie zostawiaj „tymczasowego” endpointu importującego legacy bez uwierzytelnienia i limitów.

W komunikacji między usługami negocjuj wersję na poziomie kontraktu, nie klasy runtime. Testy kompatybilności powinny używać przykładowych rekordów, a nie serializowanych obiektów zależnych od konkretnej wersji biblioteki. To ułatwia także zarządzanie łańcuchem dostaw, bo aktualizacja zależności nie zmienia ukrytej semantyki starego grafu.

Szyfrowanie i podpis nie zastępują bezpiecznego formatu

Zaszyfrowany payload może zostać odszyfrowany tuż przed niebezpiecznym parserem. Jeżeli atakujący zdobędzie możliwość legalnego tworzenia wiadomości, przejmie klucz producenta albo zmodyfikuje dane przed szyfrowaniem, kryptografia dostarczy parserowi poprawnie uwierzytelniony atak. Podobnie stary, wcześniej bezpieczny obiekt może stać się groźny po dodaniu nowej biblioteki z gadżetem.

Podpis jest nadal wartościowy: blokuje nieautoryzowaną modyfikację i wspiera audyt pochodzenia. Musi jednak działać obok prostego formatu, wąskiego schematu, limitów i izolacji. W modelu zagrożeń zapisz osobno, kto może wystawić wiadomość, kto zarządza kluczem i co stanie się po kompromitacji legalnego producenta.

Jak raportować wynik

Raport powinien wskazać sink, format, źródło danych, konfigurację typów, dostępny classpath, uprawnienia procesu i potwierdzony efekt. Rozróżnij:

  • potwierdzoną deserializację niezaufanych danych;
  • możliwy gadget chain na podstawie wersji zależności;
  • potwierdzony skutek w izolowanym laboratorium;
  • maksymalny wpływ wynikający z uprawnień usługi.

Nie oznaczaj automatycznie każdej deserializacji jako RCE. Z drugiej strony brak gotowego publicznego łańcucha nie czyni projektu bezpiecznym: nowa zależność może dostarczyć gadżet później. Przyczyna projektowa pozostaje.

Checklista

  • Czy natywna deserializacja występuje na jakiejkolwiek granicy zaufania?
  • Czy nadawca może wskazać klasę lub typ polimorficzny?
  • Czy parser ma limit bajtów, głębokości, obiektów i czasu?
  • Czy weryfikacja podpisu następuje przed parsowaniem?
  • Czy DTO odrzuca nieznane pola i nie przyjmuje pól autoryzacyjnych?
  • Czy proces parsera ma minimalne uprawnienia i ograniczony egress?
  • Czy migracja ze starego formatu ma termin usunięcia fallbacku?
  • Czy test regresyjny obejmuje nieznany typ, nadmierny graf i stary payload?

Niebezpieczna deserializacja jest błędem granicy zaufania. Jeżeli zewnętrzny zapis może decydować, jakie obiekty powstaną i co wydarzy się podczas ich tworzenia, dane zaczynają zachowywać się jak kod. Zamów test penetracyjny web i API — prześledzimy pełny przepływ danych i zaproponujemy migrację, która usuwa przyczynę, a nie tylko jeden gadget chain.

UDOSTĘPNIJ / KOPIUJ