Unsichere Deserialisierung

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