
Writeup und Exploit für CVE-2023-45777, Umgehung der Intent-Validierung innerhalb des AccountManagerService auf Android 13 trotz der „Lazy Bundle“-Abschwächung
Beginnen wir diesmal mit dem Patch, der als Fix für CVE-2023-45777 im Android Security Bulletin erschien:```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
if (intent != null && intent.getClass() != Intent.class) {
return false;
}
Wenige Leute waren neugierig genug, um mich zu fragen; zuvor habe ich ihnen mit einigen Hinweisen geantwortet, und jetzt veröffentliche ich den vollständigen Writeup zu diesem Problem
Aber zuerst etwas Kontext darüber, was in diesem Patch vor sich geht
Dies ist eine Änderung in der [`checkKeyIntent()`-Methode](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d). Diese Methode führt mehrere Prüfungen durch, um sicherzustellen, dass die von der Anwendung bereitgestellte `Intent` für das System sicher zu starten ist (unter Verwendung der Privilegien des Systems)
Zuerst verwendet diese Methode `checkKeyIntentParceledCorrectly()`, die ein `Bundle`, das wir prüfen, serialisiert und wieder deserialisiert und prüft, ob die `Intent`, die vorher aus dem `Bundle` entnommen wurde, mit der `Intent` aus dem `Bundle` nach einem solchen Zyklus übereinstimmt. Da der Start der `Intent` in anderen System-App-Prozessen erfolgt als in dem, der die Validierung durchführt, war es zuvor [möglich, `Bundle`s zu konstruieren, die während der Validierung innerhalb von `AccountManagerService` sicher erschienen, aber eine andere `Intent` enthielten, nachdem sie an den nächsten Prozess gesendet wurden](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482). Dies simuliert das Senden des `Bundle`s an den nächsten Prozess, um solche Situationen zu erkennen.
Nach `checkKeyIntentParceledCorrectly()` haben wir einen `bundle.getParcelable()`-Aufruf, den dieser Patch von der veralteten Version, die beliebige Objekte konstruieren konnte, auf eine Version umstellt, die validiert, dass das Objekt, das deserialisiert werden soll, vom Typ ist, der im zweiten Parameter angegeben wurde
Diese Version mit Typparameter wurde in Android 13 als Teil der umfassenderen `Parcel`/`Bundle`-Härtung eingeführt. Insbesondere vor Android 13 behielt ein `Bundle`, wenn es zwischen Prozessen gesendet wurde, eine rohe Kopie der gesamten serialisierten Daten, bis auf ein Element zugegriffen wurde, woraufhin jeder Wert deserialisiert wurde. Jetzt, wenn auf einen Wert zum ersten Mal zugegriffen wird, nachdem das `Bundle` empfangen wurde, werden nur die `String`-Schlüssel und die Werte primitiver Typen deserialisiert, während nicht-primitive Werte als `LazyValue`s belassen werden, deren Länge als Teil der serialisierten Daten gespeichert ist, um sicherzustellen, dass selbst bei einer Nichtübereinstimmung der Serialisierungs-/Deserialisierungslogik solche Nichtübereinstimmungen keine anderen Einträge beeinträchtigen
Bevor wir eintauchen, werfen wir einen Blick auf `LazyValue`: In seinem Quellcode [haben wir einen schönen Kommentar, der seine Datenstruktur erklärt](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
| 4B | 4B |
mSource = Parcel{... | type | length | object | ...}
a b c d
length = d - c
mPosition = a
mLength = d - a
mPosition und mLength beschreiben den Speicherort der gesamten LazyValue-Daten im ursprünglichen Parcel, einschließlich type und length. „length“ (ohne „m“ am Anfang) bezieht sich auf den Längenwert, wie er in den Parcel geschrieben wurde, und schließt den Header (type und length) aus.
Wenn ein Bundle, das eine LazyValue enthält, an einen anderen Prozess weitergeleitet wird, wird die gesamte LazyValue einschließlich der Felder type und length unverändert von Bundle.mParcelledData in den Ziel-Parcel kopiert.
Wenn auf das durch LazyValue dargestellte Bundle-Element zugegriffen wird, wird Parcel auf mPosition zurückgespult und readValue() aufgerufen. Wenn ein Typargument an bundle.getParcelable() übergeben wird, wird es an readValue() weitergegeben, das sowohl sicherstellt, dass der Typ, der entpackt werden soll, der erwartete ist, als auch nach dem Entpacken verifiziert, dass der entpackte Werttyp der erwartete ist. Nach dem Entpacken wird LazyValue ersetzt, sodass der Wert beim nächsten Schreiben des Bundle in den Parcel erneut über writeValue() serialisiert wird.
Die Verwendung des typisierten Parameters Bundle.get*()/Parcel.read*() ist hauptsächlich für Methoden wie Parcel.readParcelableList() relevant, die eine ArrayList zurückgeben. Aufgrund der Java-Typ-Erasure wird der Teil <SomeParcelableType> selbst bei etwas wie List<SomeParcelableType> field = parcel.readParcelableList(); zur Laufzeit nicht erzwungen, und eine solche List könnte beliebige im System verfügbare Parcelable-Klassen enthalten. Daher könnten alle im System verfügbaren createFromParcel/writeToParcel als Teil der Serialisierung/Deserialisierung des Typs verwendet werden, der eine solche List enthält.
Möglicherweise möchten Sie sich auch die Präsentation des Android Security and Privacy Teams zur Einführung dieser Mechanismen ansehen (Folien, Video).
Hier scheint die Verwendung der typisierten Version jedoch redundant zu sein, da wir den Typ des zurückgegebenen Objekts auch explizit prüfen. Was ist also los und welche Schwachstelle wird hier behoben?
Werfen Sie noch einmal einen Blick auf den Patch von Anfang an