Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
TheLastBundleMismatch — Writeup et exploit pour CVE-2023-45777, contournement de la validation d'Intent dans AccountManagerService sur Android 13 malgré l'atténuation « Lazy Bundle » | Kitploit
Outils/GitHubGitHub/michalbednarski/thelastbundlemismatch
Sécurité AndroidAnalyse des VulnérabilitésExploitationAnalyse de BinairesArticles et Recherche
GitHubmichalbednarski/thelastbundlemismatch

TheLastBundleMismatch

Writeup et exploit pour CVE-2023-45777, contournement de la validation d'Intent dans AccountManagerService sur Android 13 malgré l'atténuation « Lazy Bundle »

Voir le dépôt

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
10114il y a 2 ansVérifié par Kitploit

Correctif mystérieux

Commençons cette fois-ci par le correctif apparu comme solution pour CVE-2023-45777 dans le Bulletin de sécurité Android :```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();

  • root@kitploit:~
           Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
    
  • root@kitploit:~
           Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
           if (intent != null && intent.getClass() != Intent.class) {
               return false;
           }
    
root@kitploit:~
Peu de personnes ont été assez intriguées pour me le demander ; auparavant, je leur avais répondu avec quelques indices, et maintenant je publie l'analyse complète de ce problème.

Mais d'abord, apportons un peu de contexte sur ce qui se passe dans ce correctif.

Il s'agit d'un changement dans la [méthode `checkKeyIntent()`](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). Cette méthode effectue plusieurs vérifications pour s'assurer que l'`Intent` fourni par l'application est sûr pour que le système le lance (en utilisant les privilèges du système).

Tout d'abord, cette méthode utilise `checkKeyIntentParceledCorrectly()`, qui sérialise puis désérialise à nouveau le `Bundle` que nous vérifions, et s'assure que l'`Intent` extrait du `Bundle` avant cela correspond à l'`Intent` du `Bundle` après un tel cycle. Étant donné que le lancement de l'`Intent` se produit dans d'autres processus d'applications système que celui qui effectue la validation, il était auparavant [possible de construire des `Bundle` qui semblaient sûrs lors de la validation dans `AccountManagerService`, mais qui contenaient un `Intent` différent après avoir été envoyés au processus suivant](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482). Cela simule l'envoi du `Bundle` au processus suivant afin de détecter de telles situations.

Après `checkKeyIntentParceledCorrectly()`, nous avons l'appel `bundle.getParcelable()`, que ce correctif fait passer de la version obsolète qui pouvait construire n'importe quel objet à une version qui valide que l'objet sur le point d'être désérialisé est du type spécifié dans le second paramètre.

Cette version avec paramètre de type a été introduite dans Android 13, dans le cadre d'un durcissement plus large de `Parcel`/`Bundle`. En particulier, avant Android 13, lorsqu'un `Bundle` était envoyé entre processus, il conservait une copie brute de l'intégralité des données sérialisées jusqu'à ce qu'un élément soit accédé, moment auquel chaque valeur était désérialisée. Désormais, lorsqu'une valeur est accédée pour la première fois après la réception du `Bundle`, seules les clés `String` et les valeurs de types primitifs sont désérialisées, tandis que les valeurs non primitives sont laissées sous forme de `LazyValue`, dont la longueur est stockée dans les données sérialisées afin de garantir que même en cas de désynchronisation de la logique de sérialisation/désérialisation, ces désynchronisations n'affectent pas les autres entrées.

Avant d'entrer dans le vif du sujet, examinons `LazyValue` : dans son code source, [nous avons un joli commentaire expliquant sa structure de données](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 et mLength décrivent l'emplacement de l'ensemble des données LazyValue dans le Parcel d'origine, y compris type et length. « length » (sans « m » au début) fait référence à la valeur de longueur telle qu'écrite dans le Parcel et exclut l'en-tête (type et length)

Si un Bundle contenant un LazyValue est transféré vers un autre processus, l'ensemble du LazyValue y compris les champs type et length est copié tel quel de Bundle.mParcelledData vers le Parcel de destination

Lorsque l'élément du Bundle représenté par LazyValue est accédé, le Parcel est rembobiné à mPosition et readValue() est appelé. Si un argument de type est passé à bundle.getParcelable(), il est propagé à readValue() qui garantira à la fois que le type sur le point d'être désérialisé est celui attendu et vérifiera après désérialisation que le type de la valeur désérialisée est celui attendu. Après désérialisation, le LazyValue est remplacé afin que la prochaine fois que le Bundle est écrit dans un Parcel, la valeur soit sérialisée à nouveau via writeValue()

L'utilisation du paramètre typé Bundle.get*()/Parcel.read*() est surtout pertinente pour des méthodes telles que Parcel.readParcelableList(), qui renvoie un ArrayList et, en raison de l'effacement de type en Java, même si vous faisiez quelque chose comme List<SomeParcelableType> field = parcel.readParcelableList();, la partie <SomeParcelableType> n'était pas appliquée à l'exécution et une telle List pouvait contenir n'importe quelle classe Parcelable disponible dans le système, et donc tous les createFromParcel/writeToParcel disponibles dans le système pouvaient être utilisés dans le cadre de la sérialisation/désérialisation du type contenant une telle List

Vous pourriez également consulter la présentation de l'équipe Android Security and Privacy sur l'introduction de ces mécanismes (diapositives, vidéo)

Ici, cependant, l'utilisation de la version typée semble redondante, car nous vérifions également explicitement le type de l'objet renvoyé. Alors, que se passe-t-il et quelle vulnérabilité est corrigée ici ?

Effets secondaires

Revenons au début du correctif

  • Si la valeur désérialisée sous la clé "intent" est un Intent
    • Elle sera validée pour pointer vers un composant qu'il est sûr pour le système de lancer
    • Si une incohérence pouvait être déclenchée à partir de l'objet Intent, nous aurions un problème bien plus grave
  • Si la valeur désérialisée n'est pas un Intent
    • Pour faire quoi que ce soit de malveillant, nous aurions besoin d'avoir un Intent à l'intérieur du Bundle après son envoi à un autre processus, mais le type du Parcelable est enregistré à un décalage antérieur à toute incohérence possible et le préfixage de longueur du LazyValue nous empêche de modifier les paires clé-valeur suivantes en cas d'incohérence writeToParcel/createFromParcel

Alors, quelle chose dangereuse l'appel à bundle.getParcelable(AccountManager.KEY_INTENT) sans argument de type pourrait-il faire ici ?

[Réponse au paragraphe suivant, essayez de deviner avant de continuer à lire. Si j'avais un fursona, ce serait l'endroit pour un peu d'art]

La réponse est l'appel à un createFromParcel() sans rapport qui modifie en réalité les données brutes du LazyValue stocké sous une clé différente et qui sera transmis tel quel au processus suivant

Nous avons une implémentation de createFromParcel() qui peut en réalité appeler writeInt() sur le Parcel fourni

Mais pas parce que writeInt a été placé par erreur, mais en raison d'une réflexion non restreinte. En particulier, à l'intérieur de PackageParser, nous avons le code suivant :```java final Class cls = (Class) Class.forName(componentName); final Constructor cons = cls.getConstructor(Parcel.class);

intentsList = new ArrayList<>(N); for (int i = 0; i < N; ++i) { intentsList.add(cons.newInstance(in)); }

root@kitploit:~
We can have `Parcel` object which was passed to `createFromParcel` passed to any available in system `public` constructor that accepts single `Parcel` argument

And then [we have following code](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=51-56;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb):```java
public PooledStringWriter(Parcel out) {
    mOut = out;
    mPool = new HashMap<>();
    mStart = out.dataPosition();
    out.writeInt(0); // reserve space for final pool size.
}

Nous avons un constructeur qui appelle writeInt(0) sur le Parcel fourni, mais il y a quelques éléments qui compliquent l'exploitation

Tout d'abord, bien que cela ne soit pas directement visible dans le code source, immédiatement après l'appel à newInstance(), un cast est effectué et une ClassCastException est levée

Avaler l'exception

J'avais besoin de quelque chose qui, pendant createFromParcel, appellerait createFromParcel d'une autre classe dans un bloc try puis échouerait à propager l'exception capturée

C'est la partie où l'exploit ne fonctionne pas réellement sur un AOSP pur, j'ai utilisé une classe spécifique à Samsung

J'ai inclus une copie des parties pertinentes de cette classe dans ce dépôt

Ce dépôt inclut également un script qui l'intègre dans AOSP, donc pour tester, vous pouvez l'exécuter (passez le chemin vers votre checkout AOSP en argument, par ex. ./make-aosp-buggy.sh /path/to/aosp), annulez la modification décrite au début du writeup et exécutez cet exploit contre votre build AOSP

J'ai précédemment utilisé la classe OutputConfiguration d'AOSP pour avaler les exceptions, avant Android 13, avaler une exception dans createFromParcel() combiné à la possibilité de construire d'autres Parcelable-s est une vulnérabilité en soi, cependant dans le cas de SemImageClipData, l'avalement d'exception n'était pas présent sur ces versions d'Android

Il y a cependant une différence importante entre SemImageClipData et OutputConfiguration utilisé précédemment : même si SemImageClipData capture une exception, il renvoie toujours un objet non nul et s'il est ensuite casté vers un autre type, cela déclencherait une ClassCastException, ce qui est ce que nous essayons d'éviter

L'effacement de type Java frappe à nouveau

L'effacement de type Java signifie que les méthodes génériques ne connaissent pas réellement le type générique utilisé par l'appelant. Cela aidait généralement l'exploitation```java // When we read some List, this actually didn't check if list contains only SomeParcelableType List myList = sourceParcel.readParcelableList();

// Above is why Android 13 has introduced typed methods that enforce type at runtime List myList = sourceParcel.readParcelableList(SomeParcelableType.class);

// If untyped method was used when reading, list can contain non-SomeParcelableType // items and they would be written without errors targetParcel.writeParcelableList(myList, 0);

// However if List contains non-SomeParcelableType item, this would throw during item access // (That however commonly didn't happen if we used Parcelable object only as container in gadget chain) SomeParcelableType myItem = myList.get(0);

root@kitploit:~
Cette fois, l'effacement de type n'a pas joué en notre faveur. D'abord, nous avions une méthode qui invoquait en réalité le constructeur via la réflexion.```java
private static <T extends IntentInfo> ArrayList<T> createIntentsList(Parcel in) {
    // ...
    final ArrayList<T> intentsList;
    // ...
    intentsList.add(cons.newInstance(in));
    // ...
    return intentsList;
}

Cette méthode a un paramètre générique T. Peu importe le type de paramètre utilisé par l'appelant, cependant, comme dans la déclaration de cette méthode il y a <T extends IntentInfo>, la ligne avec l'appel newInstance() devient intentsList.add((IntentInfo) cons.newInstance(in));, même si newInstance() renvoie Object et que ArrayList.add() accepte Object comme argument. Cela a introduit la nécessité d'envelopper l'appel de celle-ci avec un Parcelable qui avale l'Exception.

Ensuite, nous avons l'appel bundle.getParcelable().```java @Deprecated @Nullable public T getParcelable(@Nullable String key) { unparcel(); Object o = getValue(key); if (o == null) { return null; } try { return (T) o; } catch (ClassCastException e) { typeWarning(key, o, "Parcelable", e); return null; } }

root@kitploit:~
La procédure de désérialisation est effectuée par l'appel `getValue()`, qui mène en réalité à l'appel `createFromParcel()`. Si une `ClassCastException` se produit ici, elle ne sera pas interceptée. `getValue()` retourne désormais la valeur qui a été désérialisée pour cette clé via [`parcel.readValue()`](https://developer.android.com/reference/android/os/Parcel#readValue(java.lang.ClassLoader))

Cependant, si nous plaçons `SemImageClipData` comme valeur, dans le bloc `try`-`catch`, nous tenterions de la convertir en `T`, qui dans ce cas est `Parcelable` comme déclaré dans la déclaration générique de la méthode. L'appelant utilise cette méthode comme générique avec `T` étant un `Intent`, mais `getParcelable()` ne le sait pas et la conversion vers `Intent` se produit chez l'appelant, donc une `ClassCastException` est levée en dehors du `try`

Nous pouvons cependant envelopper notre `SemImageClipData` dans un tableau `Parcelable[]`, puis la conversion vers `T` dans `getParcelable()` échouera à convertir `Parcelable[]` en `Parcelable` et lèvera une `ClassCastException` dans le `try`, cette `Exception` sera journalisée et `null` sera retourné puis accepté par `checkKeyIntent()`

# La disposition

Nous devons donc maintenant aligner les éléments dans le `Bundle` afin qu'après le cycle `writeToParcel`/`createFromParcel`, son contenu soit celui que nous avons préparé

Mais contrairement au « FengShui de `Bundle` » typique où le déclencheur consiste à faire lire par `createFromParcel` plus ou moins de données que ce que `writeToParcel` avait précédemment écrit, ici nous avons `writeInt(0)` qui écrase une partie du `LazyValue` non désérialisé

Voici donc à quoi ressemble `Bundle.mParcelledData` lorsqu'il est d'abord désempaqueté par `AccountManagerService` (les décalages sont obtenus en appelant `dataPosition()` via le débogueur attaché à `system_server`)

<table>
<tr><th>Décalage</th><th>Valeur</th><th>Note</th></tr>
<tr><td>0</td><td>3</td><td>Nombre de paires clé-valeur</td></tr>
<tr><td>4</td><td>"intent"</td><td>Première clé dans le <code>Bundle</code>, celle qui sera accédée par <code>getParcelable(AccountManager.KEY_INTENT)</code></td></tr>
<tr><td>24</td><td>16</td><td>Le premier <code>LazyValue</code> commence ici, le type est <code>VAL_PARCELABLEARRAY</code></td></tr>
<tr><td>28</td><td>340</td><td>Longueur déclarée du <code>LazyValue</code>, utilisée pour trouver la clé suivante dans le <code>Bundle</code>. Notre <code>LazyValue</code> n'aura en réalité pas cette taille après lecture, mais <code>LazyValue.apply</code> <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/Parcel.java;l=4501-4505;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">le signale via <code>Slog.wtfStack()</code></a> qui <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Slog.java;l=230-235;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">ne lève pas d'exception</a></td></tr>
<tr><td>32</td><td>1</td><td>Longueur du tableau <code>Parcelable[]</code>, le tableau n'a qu'un seul élément et est présent pour que la <code>ClassCastException</code> se produise dans le bloc <code>try</code> qui se trouve dans <code>bundle.getParcelable()</code></td></tr>
<tr><td>36</td><td>"com.samsung.android.<br>content.clipboard.data.<br>SemImageClipData"</td><td>Nom de la classe <code>Parcelable</code>, c'est la classe wrapper qui avalera l'exception</td></tr>
<tr><td>160</td><td>2</td><td>Étiquette de type utilisée par <code>createClipBoardData()</code></td></tr>
<tr><td>164</td><td></td><td>Éléments lus par le constructeur de la superclasse <code>SemImageClipData</code> (qui incluent un appel <code>readParcelable()</code>, mais cela se produit en dehors du bloc <code>try</code>). Pas vraiment pertinents, mais nous devons les traverser avant d'atteindre la partie intéressante de <code>createFromParcel()</code></td></tr>
<tr><td>252</td><td></td><td>Données lues par <code>SemImageClipData.readFromSource()</code></td></tr>
<tr><td>272</td><td>"android.content.pm.<br>PackageParser&#36;Activity"</td><td>Nom du <code>Parcelable</code> lu par <code>mExtraParcelFd = in.readParcelable()</code>. Le type ne correspond pas, mais avant que la conversion n'ait lieu, une exception sera de toute façon levée</td></tr>
<tr><td>360</td><td></td><td>Champs <code>className</code> &amp; <code>metadata</code> de <code>PackageParser$Component</code></td></tr>
<tr><td>368</td><td>1</td><td>Nombre d'éléments dans <code>createIntentsList()</code></td></tr>
<tr><td>372</td><td>"android.os.<br>PooledStringWriter"</td><td>Nom de la classe qui sera instanciée via <code>Class.forName().getConstructor(Parcel.class).newInstance()</code>. À cette position, le premier <code>LazyValue</code> se termine, mais son analyse continue car <code>readValue()</code> n'a pas atteint la fin. Interprété également comme deuxième clé dans le <code>Bundle</code> lors du <code>unparcel()</code> initial</td></tr>
<tr><td>436</td><td>4</td><td>Le deuxième <code>LazyValue</code> commence ici, ce 4 est <code>VAL_PARCELABLE</code> pour lequel <code>Parcel.isLengthPrefixed()</code> retournera <code>true</code>. Cette valeur est ensuite écrasée par le constructeur <code>PooledStringWriter</code>, après quoi une exception est levée et <code>getParcelable(AccountManager.KEY_INTENT)</code> se termine</td></tr>
<tr><td>440</td><td>240</td><td>Longueur du <code>LazyValue</code> dont le type a été déclaré comme <code>VAL_PARCELABLE</code>, utilisée pour déterminer la position de l'entrée suivante et la quantité de données à copier vers le <code>Bundle</code> cible lors de la re-sérialisation. Ce <code>LazyValue</code> n'est en réalité pas désempaqueté et sert de conteneur de données brutes</td></tr>
<tr><td>684</td><td>"1&y~pw"</td><td rowspan="2">Troisième paire clé/valeur, la clé est générée aléatoirement pour avoir un <code>hashCode()</code> Java supérieur à ceux utilisés précédemment (les éléments stockés dans <code>ArrayMap</code> sont triés par <code>hashCode()</code> croissant de la clé et c'est l'ordre dans lequel les éléments du <code>Bundle</code> seront écrits dans le <code>Parcel</code>). Cette paire clé-valeur n'est présente ici que pour augmenter le nombre total de paires écrites, car ce sera le nombre de paires lues, même si cette paire ne sera en réalité pas lue</td></tr>
<tr><td>704</td><td>-1 (<code>VAL_NULL</code>)</td></tr>
</table>

Ensuite, lorsque le Bundle est sérialisé à nouveau, il ressemble à ceci :

<table>
<tr><th>Décalage</th><th>Valeur</th><th>Note</th></tr>
<tr><td>0</td><td>3</td><td>Nombre de paires clé-valeur</td></tr>
<tr><td>4</td><td>"intent"</td><td>Première clé dans le <code>Bundle</code></td></tr>
<tr><td>24</td><td>16</td><td><code>VAL_PARCELABLEARRAY</code>, le tableau <code>Parcelable[]</code> précédemment désérialisé est maintenant sérialisé à nouveau</td></tr>
<tr><td>28</td><td>196</td><td>Longueur du <code>LazyValue</code>, c'est-à-dire notre objet <code>SemImageClipData</code> enveloppé. Cette longueur est tirée de l'exécution avec mon <code>SemImageClipData</code> simulé et donc les décalages présentés à partir de ce point ne correspondront pas à ceux qui apparaîtraient sur un véritable appareil Samsung, mais ce <code>LazyValue</code> ne sera plus désérialisé, donc cela n'a pas d'importance pour l'exécution de l'exploit</td></tr>
<tr><td>224</td><td>"android.os.<br>PooledStringWriter"</td><td>Deuxième clé dans le <code>Bundle</code></td></tr>
<tr><td>288</td><td>0</td><td>Le deuxième <code>LazyValue</code> commence ici, l'élément sous la clé <code>"android.os.PooledStringWriter"</code> n'a pas été accédé, donc ce <code>LazyValue</code> est copié depuis les données d'origine, mais l'étiquette de type a été écrasée par l'appel <code>writeInt(0)</code> effectué par le constructeur <code>PooledStringWriter</code> et à l'arrivée dans le processus cible, cela n'est plus interprété comme un <code>LazyValue</code></td></tr>
<tr><td>292</td><td>240</td><td>C'était la longueur du deuxième <code>LazyValue</code> copiée depuis le <code>Bundle</code> d'origine, mais comme l'étiquette de type a été écrasée par <code>writeInt(0)</code>, qui est <code>VAL_STRING</code>, cette valeur est maintenant lue via <code>readString()</code>. Précédemment, pour <code>LazyValue</code>, la longueur était exprimée en octets, mais maintenant, pour <code>String</code>, elle est exprimée en caractères de deux octets. Il n'y a pas assez de données dans le <code>Parcel</code> source pour cela, donc <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/Parcel.cpp;l=2221-2226;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">le <code>parcel->readString16Inplace()</code> natif échoue après avoir lu la longueur</a>, mais cela ne provoque pas d'exception côté Java</td></tr>
<tr><td>296</td><td>"intent"</td><td>« Troisième » clé dans le <code>Bundle</code>. En réalité, elle écrase la première clé : puisque <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=651-659;drc=584140c83a456b5de99880b440c2d5dfc3c70506">« intent » a un <code>hashCode()</code> plus petit que la clé vue précédemment, la méthode <code>ArrayMap.append()</code> utilise <code>put()</code> qui permet de remplacer les valeurs</a>, sinon nous aurions <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=667-675;drc=584140c83a456b5de99880b440c2d5dfc3c70506">une clé en double qui serait ensuite rejetée par <code>validate()</code></a></td></tr>
<tr><td>316</td><td>4</td><td><code>VAL_PARCELABLE</code>, ici commence le <code>LazyValue</code> contenant l'<code>Intent</code> réel qui sera lancé</td></tr>
<tr><td>536</td><td>"1&y~pw"</td><td>Élément de remplissage qui a été écrit mais n'est pas lu car les 3 paires clé-valeur ont déjà été lues. <a href="https://cs.android.com/android/_/android/platform/system/tools/aidl/+/96a02f50fdfa4d20aa46ae2dde927257eac46d4a">Contrairement aux interfaces AIDL</a>, aucun contrôle <code>enforceNoDataAvail()</code> n'est effectué sur le <code>Bundle</code> (mais même s'il y en avait un, il <a href="https://github.com/michalbednarski/ReparcelBug2/issues/3">pourrait être contourné en insérant une entrée factice spécifiant la longueur attendue</a>)</td></tr>
</table>

# Comment cela s'est produit deux fois

Discutons maintenant de quatre correctifs, dont deux corrigent la vulnérabilité dont traite ce rapport

* CVE-2023-20944 ([bulletin](https://source.android.com/docs/security/bulletin/2023-02-01#framework), [correctif](https://android.googlesource.com/platform/frameworks/base/+/d0bc9026e2e62e09fa88c1bcbf1dc1c3fb001375%5E%21/)) : Il s'agit d'une autre vulnérabilité que j'ai trouvée. Comme pour celle-ci, le correctif ne rend pas évident comment elle pourrait être exploitée, mais <a href="https://konata.github.io/posts/creator-mismatch/">il semble que quelqu'un d'autre l'ait compris (article de blog en chinois)</a>
* CVE-2023-21098 ([bulletin](https://source.android.com/docs/security/bulletin/2023-04-01#framework), [correctif](https://android.googlesource.com/platform/frameworks/base/+/107e6377328486fca55131ea06ca9d6a3c1585e0%5E%21/)) : C'est la première fois que je signale l'exploit présenté ici. Ce correctif introduit également un correctif pour le contournement de `checkKeyIntentParceledCorrectly()` applicable aux versions d'Android antérieures à 13
* CVE-2023-35669 ([bulletin](https://source.android.com/docs/security/bulletin/2023-09-01#framework), [correctif](https://android.googlesource.com/platform/frameworks/base/+/f810d81839af38ee121c446105ca67cb12992fc6%5E%21/)) : Celui-ci n'est pas en réponse à mon rapport, mais je pense qu'il a été fait pour corriger le même problème que CVE-2023-20944, mais pour les cas où `AccountManager.KEY_INTENT` est lancé par des activités autres que `ChooseTypeAndAccountActivity` (par exemple [`AddAccountSettings`](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java;l=95-107;drc=32813a2bef49b172aed89122b4eb50bf14026ddc), que j'ai manquée lors de la première signalisation du bug). Ce changement a remplacé l'utilisation de `bundle.getParcelable()` typé par l'utilisation de la version non typée et un contrôle manuel `getClass() != Intent.class`, ce qui a en réalité annulé le correctif de CVE-2023-21098
* CVE-2023-45777 ([bulletin](https://source.android.com/docs/security/bulletin/2023-12-01#framework), [correctif](https://android.googlesource.com/platform/frameworks/base/+/f4644b55d36a549710ba35b6fb797ba744807da6%5E%21/)) : C'est la deuxième fois que je signale cet exploit. Le correctif a conservé le contrôle manuel `getClass() != Intent.class`, mais en plus, il a rétabli l'utilisation de `bundle.getParcelable()` typé, ce qui est une bonne façon de corriger les deux problèmes

Bien que le même exploit fonctionne pour CVE-2023-21098 et CVE-2023-45777, la manière dont il a contourné `checkKeyIntentParceledCorrectly()` diffère

Dans le cas de CVE-2023-21098, [`checkKeyIntent()` n'était en réalité pas appelé si le `Bundle` vérifié ne contenait pas d'`Intent`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=3519-3521;drc=cdd30b5c040ba7ebd0a1cc6009183ff602434fc0). Comme `checkKeyIntent()` est ce qui appelle `checkKeyIntentParceledCorrectly()`, dans le cas où le `Bundle` d'origine ne semblait pas contenir d'`Intent`, le `Bundle` après re-sérialisation n'était pas vérifié

Dans le cas de CVE-2023-45777, `checkKeyIntentParceledCorrectly()` était correctement appelé, cependant [`writeBundle()` s'y produisait avant l'appel `getParcelable()` sans argument de type (jusqu'auquel le `Bundle` ne changeait pas son contenu)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4921-4929;drc=b0f6558fb36eb76df35c516ec5a65030a34a8734)
Télécharger l’outil