Catégorie OWASP : MASVS-CODE : qualité du code
Présentation
Lorsque vous stockez ou transférez de grandes quantités de données d'objets Java, il est souvent plus efficace de commencer par les sérialiser. Les données subissent ensuite un processus de désérialisation par l'application, l'activité ou le fournisseur destinataire qui finit par les traiter. Dans des conditions normales, les données sont sérialisées, puis désérialisées sans intervention de l'utilisateur. Toutefois, la relation de confiance entre le processus de désérialisation et son objet prévu peut être exploitée par un acteur malveillant qui pourrait, par exemple, intercepter et modifier des objets sérialisés. Cela permettrait à l'acteur malveillant d'effectuer des attaques telles que le déni de service (DoS), l'escalade de privilèges et l'exécution de code à distance (RCE).
Bien que la classe Serializable
soit une méthode courante pour gérer la sérialisation, Android dispose de sa propre classe pour gérer la sérialisation, appelée Parcel
. À l'aide de la classe Parcel
, les données d'objet peuvent être sérialisées en données de flux d'octets et empaquetées dans un Parcel
à l'aide de l'interface Parcelable
.
Cela permet de transporter ou de stocker le Parcel
plus efficacement.
Toutefois, vous devez faire preuve de prudence lorsque vous utilisez la classe Parcel
, car elle est censée être un mécanisme de transport IPC hautement efficace, mais ne doit pas être utilisée pour stocker des objets sérialisés dans le stockage persistant local, car cela pourrait entraîner des problèmes de compatibilité ou une perte de données. Lorsque les données doivent être lues, l'interface Parcelable
peut être utilisée pour désérialiser le Parcel
et le transformer à nouveau en données d'objet.
Il existe trois vecteurs principaux pour exploiter la désérialisation dans Android:
- Profiter de l'hypothèse incorrecte d'un développeur selon laquelle la désérialisation d'objets provenant d'un type de classe personnalisé est sûre. En réalité, tout objet provenant de n'importe quelle classe peut être potentiellement remplacé par du contenu malveillant qui, dans le pire des cas, peut interférer avec les chargeurs de même classe ou d'autres applications. Cette interférence se traduit par l'injection de valeurs dangereuses qui, selon l'objectif de la classe, peuvent entraîner, par exemple, une exfiltration de données ou une prise de contrôle de compte.
- Exploiter des méthodes de désérialisation considérées comme non sécurisées à la conception (par exemple, CVE-2023-35669, une faille d'élévation des privilèges locaux qui permettait l'injection de code JavaScript arbitraire via un vecteur de désérialisation de liens profonds)
- Exploiter des failles dans la logique de l'application (par exemple, CVE-2023-20963, une faille d'escalade de privilèges locale qui permettait à une application de télécharger et d'exécuter du code dans un environnement privilégié via une faille dans la logique de parcelle WorkSource d'Android).
Impact
Toute application qui désérialise des données sérialisées non fiables ou malveillantes peut être vulnérable à l'exécution de code à distance ou aux attaques par déni de service.
Risque: désérialisation d'entrée non approuvée
Un pirate informatique peut exploiter le manque de vérification des paquets dans la logique de l'application afin d'injecter des objets arbitraires qui, une fois désérialisés, pourraient forcer l'application à exécuter du code malveillant pouvant entraîner un déni de service (DoS), une escalade de privilèges et une exécution de code à distance (RCE).
Ces types d'attaques peuvent être subtils. Par exemple, une application peut contenir un intent qui n'attend qu'un seul paramètre qui, après avoir été validé, sera désérialisé. Si un pirate informatique envoie un second paramètre supplémentaire malveillant inattendu en plus de celui attendu, tous les objets de données injectés sont désérialisés, car l'intent traite les extras en tant que Bundle
. Un utilisateur malveillant peut utiliser ce comportement pour injecter des données d'objet qui, une fois désérialisées, peuvent entraîner une attaque par déserialisation, une compromission ou une perte de données.
Stratégies d'atténuation
Il est recommandé de supposer que toutes les données sérialisées ne sont pas fiables et potentiellement malveillantes. Pour garantir l'intégrité des données sérialisées, effectuez des vérifications sur les données pour vous assurer qu'elles correspondent à la classe et au format attendus par l'application.
Une solution envisageable consiste à implémenter le modèle d'anticipation pour la bibliothèque java.io.ObjectInputStream
. En modifiant le code responsable de la désérialisation, vous pouvez vous assurer que seul un ensemble de classes explicitement spécifié est désérialisé dans l'intent.
Depuis Android 13 (niveau d'API 33), plusieurs méthodes ont été mises à jour dans la classe Intent
. Elles sont considérées comme des alternatives plus sûres aux méthodes plus anciennes et désormais obsolètes de traitement des colis. Ces nouvelles méthodes plus sûres, telles que getParcelableExtra(java.lang.String, java.lang.Class)
et getParcelableArrayListExtra(java.lang.String, java.lang.Class)
, effectuent des vérifications de type de données pour détecter les faiblesses d'incohérences susceptibles de provoquer le plantage des applications et potentiellement d'être exploitées pour effectuer des attaques d'élévation des privilèges, telles que CVE-2021-0928.
L'exemple suivant montre comment une version sécurisée de la classe Parcel
peut être implémentée:
Supposons que la classe UserParcelable
implémente Parcelable
et crée une instance de données utilisateur qui est ensuite écrite dans un Parcel
. La méthode suivante, plus sûre de typage de readParcelable
, peut ensuite être utilisée pour lire le colis sérialisé:
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);
Notez dans l'exemple Java ci-dessus l'utilisation de UserParcelable.CREATOR
dans la méthode. Ce paramètre obligatoire indique à la méthode readParcelable
le type attendu et est plus performant que la version obsolète de la méthode readParcelable
.
Risques spécifiques
Cette section présente les risques qui nécessitent des stratégies d'atténuation non standards ou qui ont été atténués à certains niveaux du SDK et qui sont ici complets.
Risque: désérialisation d'objets indésirables
L'implémentation de l'interface Serializable
dans une classe entraîne automatiquement l'implémentation de l'interface par tous les sous-types de la classe donnée. Dans ce scénario, certains objets peuvent hériter de l'interface mentionnée ci-dessus, ce qui signifie que des objets spécifiques qui ne sont pas destinés à être désérialisés seront toujours traités.
Cela peut augmenter par inadvertance la surface d'attaque.
Stratégies d'atténuation
Si une classe hérite de l'interface Serializable
, conformément aux conseils de l'OWASP, la méthode readObject
doit être implémentée comme suit pour éviter qu'un ensemble d'objets de la classe puisse être désérialisé:
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");
}
Ressources
- Parcelables
- Colis
- Serializable
- Intent
- Failles de désérialisation Android: une brève histoire
- Android Parcels: le bon, le mauvais et l'amélioration (vidéo)
- Android Parcels: le bon, le mauvais et l'amélioration (diapositives de présentation)
- CVE-2014-7911: Élévation des privilèges dans Android <5.0 à l'aide d'ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: Incohérence de sérialisation/désérialisation des composants
- Conseils de l'OWASP