OWASP-Kategorie: MASVS-CODE: Codequalität
Übersicht
Wenn große Mengen an Java-Objektdaten gespeichert oder übertragen werden, ist es oft effizienter, die Daten zuerst zu serialisieren. Die Daten werden dann von der empfangenden Anwendung, Aktivität oder dem empfangenden Anbieter deserialisiert, die bzw. der die Daten schließlich verarbeitet. Normalerweise werden Daten ohne Nutzereingriff serialisiert und dann deserialisiert. Das Vertrauensverhältnis zwischen dem Deserialisierungsprozess und dem beabsichtigten Objekt kann jedoch von einem böswilligen Akteur missbraucht werden, der beispielsweise serialisierte Objekte abfangen und ändern kann. So könnten Angreifer Angriffe wie Denial-of-Service (DoS), Rechteausweitung und Remote-Code-Ausführung (RCE) ausführen.
Die Klasse Serializable
ist eine gängige Methode zum Verwalten der Serialization. Android hat jedoch eine eigene Klasse für die Serialization namens Parcel
. Mithilfe der Klasse Parcel
können Objektdaten in Bytestream-Daten serialisiert und über die Schnittstelle Parcelable
in eine Parcel
verpackt werden.
So können die Parcel
effizienter transportiert oder gelagert werden.
Die Verwendung der Parcel
-Klasse sollte jedoch sorgfältig überlegt werden. Sie ist als hocheffizienter IPC-Transportmechanismus gedacht, sollte aber nicht zum Speichern serialisierter Objekte im lokalen persistenten Speicher verwendet werden, da dies zu Datenkompatibilitätsproblemen oder Datenverlusten führen kann. Wenn die Daten gelesen werden müssen, kann die Parcelable
-Schnittstelle verwendet werden, um das Parcel
zu deserialisieren und wieder in Objektdaten umzuwandeln.
Es gibt drei Hauptvektoren für die Ausnutzung der Deserialisierung in Android:
- Ausnutzung der falschen Annahme eines Entwicklers, dass die Deserialisierung von Objekten aus einem benutzerdefinierten Klassentyp sicher ist. In der Realität kann jedes Objekt, das von einer Klasse stammt, potenziell durch schädliche Inhalte ersetzt werden, die im schlimmsten Fall die Class Loader derselben oder anderer Anwendungen beeinträchtigen können. Dazu werden gefährliche Werte eingeschleust, die gemäß dem Klassenzweck beispielsweise zur Daten-Exfiltration oder Kontoübernahme führen können.
- Ausnutzung von Deserialisierungsmethoden, die von Natur aus als unsicher gelten (z. B. CVE-2023-35669, eine Sicherheitslücke zur lokalen Berechtigungseskalierung, die die Einschleusung beliebigen JavaScript-Codes über einen Deeplink-Deserialisierungsvektor ermöglichte)
- Ausnutzung von Fehlern in der Anwendungslogik (z. B. CVE-2023-20963, ein Fehler bei der lokalen Berechtigungseskalierung, der es einer App ermöglichte, Code in einer privilegierten Umgebung über einen Fehler in der WorkSource-Paketlogik von Android herunterzuladen und auszuführen)
Positiv beeinflussen
Jede Anwendung, die nicht vertrauenswürdige oder schädliche serialisierte Daten deserialisiert, kann anfällig für Remotecodeausführung oder Denial-of-Service-Angriffe sein.
Risiko: Deserialisierung nicht vertrauenswürdiger Eingaben
Ein Angreifer kann das Fehlen der Paketüberprüfung in der Anwendungslogik ausnutzen, um beliebige Objekte einzuschleusen, die nach der Deserialisierung die Anwendung zwingen könnten, schädlichen Code auszuführen, der zu DoS (Denial of Service), Rechteausweitung und Remote-Codeausführung (Remote Code Execution, RCE) führen kann.
Diese Art von Angriffen kann subtil sein. Eine Anwendung kann beispielsweise einen Intent enthalten, für den nur ein Parameter erwartet wird, der nach der Validierung deserialisiert wird. Wenn ein Angreifer neben dem erwarteten Parameter einen zweiten, unerwarteten schädlichen zusätzlichen Parameter sendet, werden alle eingeschleusten Datenobjekte deserialisiert, da die Intent-Funktion die Extras als Bundle
behandelt. Ein böswilliger Nutzer kann dieses Verhalten nutzen, um Objektdaten einzuschleusen, die nach der Deserialisierung zu einer Remote-Code-Ausführung, Datenmanipulation oder Datenverlust führen können.
Abhilfemaßnahmen
Es hat sich bewährt, davon auszugehen, dass alle serialisierten Daten nicht vertrauenswürdig und potenziell schädlich sind. Um die Integrität der serialisierten Daten zu gewährleisten, müssen Sie die Daten prüfen, um sicherzustellen, dass sie der richtigen Klasse und dem von der Anwendung erwarteten Format entsprechen.
Eine mögliche Lösung wäre die Implementierung des Vorschaumusters für die java.io.ObjectInputStream
-Bibliothek . Wenn Sie den Code ändern, der für die Deserialisierung verantwortlich ist, können Sie dafür sorgen, dass nur eine explizit angegebene Gruppe von Klassen innerhalb des Intents deserialisiert wird.
Ab Android 13 (API-Level 33) wurden mehrere Methoden in der Klasse Intent
aktualisiert. Sie gelten als sicherere Alternativen zu älteren und jetzt veralteten Methoden zur Paketbehandlung. Bei diesen neuen typsicheren Methoden wie getParcelableExtra(java.lang.String, java.lang.Class)
und getParcelableArrayListExtra(java.lang.String, java.lang.Class)
werden Datentypen geprüft, um Schwachstellen zu erkennen, die nicht übereinstimmende Anwendungen verursachen und dazu führen können, dass Anwendungen abstürzen und für Angriffe zur Rechteausweitung wie CVE-2021-0928 ausgenutzt werden können.
Das folgende Beispiel zeigt, wie eine sichere Version der Klasse Parcel
implementiert werden kann:
Angenommen, die Klasse UserParcelable
implementiert Parcelable
und erstellt eine Instanz mit Nutzerdaten, die dann in eine Parcel
geschrieben wird. Mit der folgenden typsicheren Methode von readParcelable
kann das serialisierte Paket dann gelesen werden:
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);
Beachten Sie im Java-Beispiel oben die Verwendung von UserParcelable.CREATOR
in der Methode. Dieser erforderliche Parameter gibt der readParcelable
-Methode an, welchen Typ sie erwarten kann. Er ist leistungsfähiger als die jetzt eingestellte Version der readParcelable
-Methode.
Spezifische Risiken
In diesem Abschnitt werden Risiken zusammengefasst, für die nicht standardmäßige Risikominderungsstrategien erforderlich sind oder die auf einer bestimmten SDK-Ebene gemindert wurden. Er dient der Vollständigkeit.
Risiko: Deserialisierung unerwünschter Objekte
Wenn Sie die Serializable
-Schnittstelle in einer Klasse implementieren, wird die Schnittstelle automatisch von allen Untertypen der jeweiligen Klasse implementiert. In diesem Szenario können einige Objekte die oben genannte Schnittstelle erben. Das bedeutet, dass bestimmte Objekte, die nicht deserialisiert werden sollen, trotzdem verarbeitet werden.
Dies kann die Angriffsfläche unbeabsichtigt vergrößern.
Abhilfemaßnahmen
Wenn eine Klasse die Serializable
-Schnittstelle erbt, sollte die readObject
-Methode gemäß den OWASP-Richtlinien so implementiert werden, dass eine Reihe von Objekten in der Klasse nicht deserialisiert werden kann:
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");
}
Ressourcen
- Parzellen
- Parcel
- Serializable
- Absicht
- Android-Deserialisierungslücken: Eine kurze Geschichte
- Android Parcels: The Bad, the Good and the Better (Video)
- Android-Parzellen: Schlechte, gute und bessere (Präsentationsfolien)
- CVE-2014-7911: Rechteausweitung bei Android < 5.0 mit ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: Nicht übereinstimmende Paketserialisierung/-deserialisierung
- OWASP-Leitfaden