Catégorie OWASP : MASVS-CODE : qualité du code
Présentation
Lorsque vous stockez ou transférez de grandes quantités de données d'objet Java, il est souvent plus efficace de sérialiser les données au préalable. Les données sont ensuite désérialisées par l'application, l'activité ou le fournisseur de réception qui finit par les gérer. Dans des circonstances normales, les données sont sérialisées, puis désérialisées sans aucune 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 à la personne malveillante de mener des attaques telles que le déni de service (DoS), l'élévation des privilèges et l'exécution de code à distance (RCE).
Bien que la classe Serializable soit une méthode courante de gestion de la
sérialisation, Android possède sa propre classe de gestion de 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 compressées dans un Parcel à l'aide de l'interface Parcelable.
Cela permet de transporter ou de stocker le Parcel plus efficacement.
Néanmoins, il convient d'être prudent lors de l'utilisation de la classe Parcel, car elle est conçue pour être un mécanisme de transport IPC à haute efficacité, 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 reconvertir en données d'objet.
Il existe trois vecteurs principaux d'exploitation de la désérialisation dans Android :
- Exploiter 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 d'une classe peut potentiellement être remplacé par du contenu malveillant qui, dans le pire des cas, peut interférer avec les chargeurs de classe de la même application ou d'autres applications. Cette interférence prend la forme d'une 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 par 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 lien profond )
- Exploiter des failles dans la logique de l'application (par exemple CVE-2023-20963, une faille d'élévation des privilèges locaux 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 paquet WorkSource d'Android).
Impact
Toute application qui désérialise des données sérialisées non approuvées ou malveillantes peut être vulnérable aux attaques par exécution de code à distance ou par déni de service.
Risque : désérialisation d'une entrée non approuvée
Un pirate informatique peut exploiter l'absence de validation 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 élévation des privilèges et une exécution de code à distance (RCE).
Ces types d'attaques peuvent être subtils. Par exemple, une application peut contenir une intention n'attendant qu'un seul paramètre qui, après validation, sera désérialisé. Si un pirate informatique envoie un deuxième paramètre supplémentaire malveillant inattendu avec celui attendu, tous les objets de données injectés seront désérialisés, car l'intention traite les extras comme un
Bundle. Un utilisateur malveillant peut exploiter ce comportement pour injecter des données d'objet qui, une fois désérialisées, peuvent entraîner une RCE, une compromission ou une perte de données.
Stratégies d'atténuation
Par mesure de précaution, partez du principe que toutes les données sérialisées ne sont pas approuvées et sont 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'il s'agit de la classe et du format corrects attendus par l'application.
Une solution possible consiste à implémenter le modèle de prévision pour la
java.io.ObjectInputStream bibliothèque. En modifiant le code responsable de la
désérialisation, vous pouvez vous assurer que seul un ensemble de
classes spécifié explicitement est désérialisé dans l'intention.
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 pour la gestion des paquets. 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'incompatibilité qui pourraient entraîner le plantage des applications et potentiellement être exploitées pour effectuer des attaques d'élévation des privilèges, telles
que CVE-2021-0928.
L'exemple suivant montre comment implémenter une version sécurisée de la classe Parcel :
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 de sûreté du typage de readParcelable peut ensuite être utilisée pour lire le paquet 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 à attendre et est plus performant que la version désormais 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'objet indésirable
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 susmentionnée, 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 involontairement la surface d'attaque.
Stratégies d'atténuation
Si une classe hérite de l'interface Serializable, conformément aux recommandations de l'OWASP, la méthode readObject doit être implémentée comme suit afin d'
éviter qu'un ensemble d'objets de la classe ne soit 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
- Parcel
- Serializable
- Intention
- Android Deserialization Vulnerabilities: A Brief history
- Android Parcels: The Bad, the Good and the Better (video)
- Android Parcels: The Bad, the Good and the Better (presentation slides)
- CVE-2014-7911: Android <5.0 Privilege Escalation using ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: Parcel Serialization/Deserialization Mismatch
- Recommandations de l'OWASP