Kategoria OWASP: MASVS-CODE: Jakość kodu
Przegląd
Podczas przechowywania lub przesyłania dużych ilości danych obiektów Java często bardziej wydajne jest najpierw serializowanie danych. Dane zostaną następnie poddane procesowi deserializacji przez aplikację, działanie lub dostawcę, który ostatecznie je przetworzy. W normalnych warunkach dane są serializowane, a następnie deserializowane bez interwencji użytkownika. Jednak relacja zaufania między procesem deserializacji a jego docelowym obiektem może zostać wykorzystana przez nieuczciwy podmiot, który może na przykład przechwycić i zmienić serializowane obiekty. Umożliwiłoby to nieuczciwemu podmiotowi przeprowadzenie ataków takich jak atak typu DoS, eskalacja uprawnień i wykonanie zdalnego kodu.
Klasa Serializable jest powszechnie stosowana do zarządzania serializacją, ale Android ma własną klasę do obsługi serializacji o nazwie Parcel. Za pomocą klasy Parcel dane obiektu można serializować do danych strumienia bajtów i pakować do klasy Parcel za pomocą interfejsu Parcelable.
Umożliwia to bardziej efektywny transport i przechowywanie Parcel.
Należy jednak zachować ostrożność podczas korzystania z klasy Parcel, ponieważ jest ona przeznaczona do wydajnego przesyłania danych między procesami, ale nie powinna być używana do przechowywania serializowanych obiektów w lokalnej pamięci trwałej, ponieważ może to prowadzić do problemów z kompatybilnością danych lub ich utraty. Gdy dane muszą zostać odczytane, interfejs Parcelable może zostać użyty do deserializacji Parcel i przekształcenia go z powrotem w dane obiektu.
W Androidzie istnieją 3 główne sposoby wykorzystania deserializacji:
- Wykorzystanie błędnego założenia dewelopera, że deserializacja obiektów pochodzących z niestandardowego typu klasy jest bezpieczna. W rzeczywistości każdy obiekt pochodzący z dowolnej klasy może zostać zastąpiony złośliwą treścią, która w najgorszym przypadku może zakłócać działanie tego samego lub innych programów ładujących klasy aplikacji. Ta ingerencja polega na wstrzykiwaniu niebezpiecznych wartości, które zgodnie z przeznaczeniem klasy mogą prowadzić np. do wydobycia danych lub przejęcia konta.
- Wykorzystywanie metod deserializacji, które z założenia są niebezpieczne (np. CVE-2023-35669, lokalna luka w zabezpieczeniach umożliwiająca eskalację uprawnień, która pozwalała na wstrzykiwanie dowolnego kodu JavaScript za pomocą wektora deserializacji precyzyjnego linku).
- Wykorzystywanie błędów w logice aplikacji (np. CVE-2023-20963, lokalny błąd eskalacji uprawnień, który umożliwiał aplikacji pobieranie i wykonywanie kodu w środowisku z podwyższonymi uprawnieniami z powodu błędu w logice pakietu WorkSource w Androidzie).
Wpływ
Każda aplikacja, która deserializuje niezaufane lub złośliwe dane serializowane, może być podatna na ataki typu DoS lub wykonanie zdalnego kodu.
Ryzyko: deserializacja niezaufanych danych wejściowych
Atakujący może wykorzystać brak weryfikacji przesyłki w logice aplikacji, aby wstrzyknąć dowolne obiekty, które po deserializacji mogą zmusić aplikację do wykonania złośliwego kodu, co może spowodować atak typu DoS, eskalację uprawnień i zdalne wykonanie kodu (RCE).
Ataki tego typu mogą być subtelne. Na przykład aplikacja może zawierać intencję oczekującą tylko jednego parametru, który po zweryfikowaniu zostanie zdeserializowany. Jeśli atakujący wyśle drugi, nieoczekiwany złośliwy dodatkowy parametr wraz z oczekiwanym, spowoduje to deserializację wszystkich wstrzykniętych obiektów danych, ponieważ intencja traktuje dodatkowe parametry jako Bundle. Złośliwy użytkownik może wykorzystać to zachowanie do wstrzyknięcia danych obiektu, które po deserializacji mogą prowadzić do zdalnego wykonania kodu, naruszenia bezpieczeństwa danych lub ich utraty.
Środki ograniczające ryzyko
Zgodnie z dobrą praktyką przyjmij, że wszystkie serializowane dane są niezaufane i potencjalnie złośliwe. Aby zapewnić integralność serializowanych danych, przeprowadź weryfikację danych, aby upewnić się, że mają one odpowiednią klasę i format oczekiwany przez aplikację.
Możliwym rozwiązaniem może być wdrożenie wzorca wyprzedzającego dla java.io.ObjectInputStream biblioteki. Modyfikując kod odpowiedzialny za deserializację, możesz mieć pewność, że w ramach intencji deserializowany jest tylko wyraźnie określony zestaw klas.
W Androidzie 13 (poziom [interfejsu] API 33) zaktualizowano kilka metod w klasie Intent, które są uważane za bezpieczniejsze alternatywy dla starszych i obecnie wycofanych metod obsługi pakietów. Te nowe, bezpieczniejsze pod względem typów metody, takie jak getParcelableExtra(java.lang.String, java.lang.Class) i getParcelableArrayListExtra(java.lang.String, java.lang.Class), przeprowadzają sprawdzanie typu danych, aby wykryć niezgodności, które mogą powodować awarie aplikacji i potencjalnie być wykorzystywane do przeprowadzania ataków polegających na eskalacji uprawnień, takich jak CVE-2021-0928.
Poniższy przykład pokazuje, jak można zaimplementować bezpieczną wersję klasy Parcel:
Załóżmy, że klasa UserParcelable implementuje interfejs Parcelable i tworzy instancję danych użytkownika, która jest następnie zapisywana w obiekcie Parcel. Do odczytania zserializowanego pakietu można użyć bezpieczniejszej metody readParcelable:
Kotlin
val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)
Java
Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);
Zwróć uwagę, że w przykładzie w języku Java powyżej w metodzie użyto UserParcelable.CREATOR. Ten wymagany parametr informuje metodę readParcelable, jakiego typu danych oczekuje. Jest on wydajniejszy niż wycofana już wersja metody readParcelable.
Konkretne zagrożenia
W tej sekcji znajdziesz zagrożenia, które wymagają niestandardowych strategii ograniczania ryzyka lub zostały ograniczone na określonym poziomie pakietu SDK i są tu wymienione dla pełności informacji.
Ryzyko: niechciana deserializacja obiektu
Zaimplementowanie interfejsu Serializable w klasie spowoduje automatyczne zaimplementowanie tego interfejsu przez wszystkie podtypy danej klasy. W tym scenariuszu niektóre obiekty mogą dziedziczyć wspomniany interfejs, co oznacza, że określone obiekty, które nie powinny być deserializowane, nadal będą przetwarzane.
Może to nieumyślnie zwiększyć obszar ataku.
Środki ograniczające ryzyko
Jeśli klasa dziedziczy interfejs Serializable, zgodnie z wytycznymi OWASP, metoda readObject powinna być zaimplementowana w następujący sposób, aby uniknąć deserializacji zestawu obiektów w klasie:
Kotlin
@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
throw IOException("Cannot be deserialized")
}
Java
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Zasoby
- Parcelables
- Parcel
- Serializable
- Zamiar
- Luki w zabezpieczeniach deserializacji w Androidzie: krótka historia
- Android Parcels: The Bad, the Good and the Better (film)
- Android Parcels: The Bad, the Good and the Better (slajdy prezentacji)
- CVE-2014-7911: Android w wersji starszej niż 5.0 – eskalacja uprawnień za pomocą ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: Niezgodność serializacji/deserializacji pakietu
- Wskazówki OWASP