Aller au contenu

CWE-502 : désérialisation de données non fiables

CWE-502 : désérialisation de données non fiables

Section intitulée « CWE-502 : désérialisation de données non fiables »

Quinzième du CWE Top 25 2025. Le libellé officiel est Deserialization of Untrusted Data, « désérialisation de données non fiables ». Le catalogue recense les appellations propres à chaque écosystème : unmarshalling dans le monde Java, unpickling en Python, PHP object injection en PHP. Trois cultures, un seul malentendu : croire qu’on lit des données alors qu’on reconstruit des objets.

→ Lire l’explication complète de la marmotte

Le produit désérialise des données non fiables sans s’assurer suffisamment que les données résultantes seront valides.

La formulation est prudente, et il faut la lire deux fois pour en mesurer la portée. Sérialiser, c’est transformer un objet en mémoire (avec son type, ses attributs, ses références) en une suite d’octets transportable. Désérialiser, c’est l’opération inverse : reconstruire l’objet. Or reconstruire un objet n’est pas remplir une structure de données : c’est instancier une classe, parfois exécuter du code de reconstruction, et toujours faire confiance au flux pour décider quel type d’objet fabriquer.

C’est là que la classe se sépare des injections, auxquelles on la rattache spontanément à tort. Dans une injection, l’attaquant fournit du texte qu’un interpréteur exécute. Ici, il ne fournit aucun code : il fournit un état qui désigne des classes déjà présentes dans l’application, et dont l’enchaînement produit l’effet voulu. Rien d’étranger n’a été introduit ; on a seulement demandé au programme de s’assembler autrement.

La classe couvre aussi bien les formats binaires propriétaires que les formats textuels dès lors qu’ils permettent de désigner un type : ce qui est le cas de certains usages de YAML, où un marqueur de type suffit à faire instancier n’importe quelle classe.

CWE-502 est une base dont le parent est la classe CWE-913, « contrôle incorrect des ressources de code gérées dynamiquement ». Ce rattachement est l’information la plus utile de la fiche : le catalogue range cette faiblesse non pas avec les injections, mais avec les défauts de contrôle du code dynamique, aux côtés de l’injection de code, qui partage ce même parent. Les deux disent la même chose sous deux formes : l’application décide à l’exécution de ce qu’elle exécute, et l’extérieur influence cette décision.

Sa voisine la plus instructive est CWE-915, « modification mal contrôlée d’attributs d’objet déterminés dynamiquement », donnée comme pair. C’est la famille de l’affectation massive d’attributs et de la pollution de prototype en JavaScript : on ne reconstruit pas un objet entier, on écrase quelques attributs choisis d’objets existants. Même principe, portée différente.

Pourquoi elle est si haut malgré sa réputation de problème résolu

Section intitulée « Pourquoi elle est si haut malgré sa réputation de problème résolu »

Parce que la correction est conditionnée à un inventaire. La parade recommandée (n’accepter à la désérialisation qu’une liste fermée de classes autorisées) suppose de savoir quelles classes l’application a besoin de reconstruire. Cet inventaire n’existe presque jamais, et l’établir dans une application ancienne qui échange des objets entre services demande un travail que personne ne finance.

Parce que les chaînes exploitables viennent des dépendances. L’application n’a pas besoin de contenir du code dangereux : il suffit qu’une de ses bibliothèques, ou une bibliothèque de ses bibliothèques, contienne une classe dont la reconstruction déclenche une action utile à l’attaquant. Autrement dit, la vulnérabilité d’une application peut apparaître sans que son code change, par la seule mise à jour d’une dépendance : c’est un cas rare et désagréable, qui relie cette classe à la question des dépendances transitives.

Parce que la gravité est maximale et le vecteur discret. Le flux sérialisé circule dans un cookie, un champ caché, un message de file d’attente, un en-tête : rien qui ressemble à du code dans les journaux. Le score du classement retient surtout la gravité, et elle est systématiquement critique.

Le site traite le mécanisme complet dans désérialisation non sécurisée et chaîne de gadgets : ce qu’est un gadget, comment des classes inoffensives s’enchaînent, et pourquoi la correction ne consiste pas à filtrer le flux. La variante dans un format apparemment anodin est traitée par l’attaque par désérialisation YAML, qui montre qu’un fichier de configuration peut instancier des objets.

Pour la dimension réseau (le fait que ces attaques traversent les filtres parce qu’elles ressemblent à du trafic applicatif normal), bloquer les requêtes côté serveur et la désérialisation explique pourquoi un pare-feu ne voit rien.

Ne pas désérialiser de données non fiables. C’est la seule réponse complète, et elle est plus accessible qu’il n’y paraît : la plupart des échanges qui emploient un format sérialisé natif pourraient employer un format de données pur, une structure de champs typés, sans notion de classe. Le travail consiste à remplacer un transport d’objets par un transport de données, puis à construire les objets soi-même.

Fermer la liste des classes autorisées quand le format natif est inévitable, par les mécanismes de filtrage fournis par la plateforme, et la maintenir comme un élément de sécurité : c’est-à-dire la relire à chaque ajout.

Signer et vérifier les flux qui doivent circuler entre services de confiance. Cela ne protège pas d’un service compromis, mais cela ferme la porte au flux forgé depuis l’extérieur, qui est le cas de loin le plus fréquent.

Surveiller les dépendances, puisque la surface exploitable vit chez elles : un inventaire des composants réellement exécutés est ce qui permet de savoir, le jour où une chaîne est publiée, si l’on est concerné.

Une équipe qui voit dans la désérialisation un simple décodage l’emploie partout où il faut transporter un objet : entre deux services, dans un cookie de session, dans un message de file d’attente. Le défaut ne se manifeste jamais à l’usage, puisque le format fonctionne parfaitement et qu’il n’existe aucun moyen de distinguer, dans les journaux, un flux légitime d’un flux forgé. Le jour de l’incident, l’équipe découvre deux choses coûteuses à la fois : qu’elle ne sait pas quelles classes son application est capable de reconstruire, donc qu’elle ne peut pas fermer la liste sans risquer de casser la production ; et que la chaîne exploitée se trouve dans une dépendance de dépendance qu’elle n’a pas choisie, de sorte que la correction suppose une mise à jour dont elle ne maîtrise ni le calendrier ni les effets. Entre les deux, le service reste exposé à une exécution de code à distance par un cookie.

Un programme manipule des objets : un client, une commande, un panier, chacun avec ses propriétés et ses liens vers d’autres objets. Pour envoyer un tel objet à un autre programme, ou le conserver, il faut le mettre à plat sous forme d’une suite de caractères. Cette opération s’appelle la sérialisation, et l’opération inverse (reconstruire l’objet à partir de la suite de caractères), la désérialisation.

Le piège est que la reconstruction n’est pas un simple remplissage de cases. La suite de caractères indique quel genre d’objet fabriquer, et le programme obéit : il va chercher le modèle correspondant et le construit, en exécutant au passage le code prévu pour cette construction.

Si cette suite de caractères vient de l’extérieur (un cookie, un formulaire, un message reçu) celui qui l’a écrite choisit donc les objets que le programme va fabriquer. Il ne fournit aucun programme de son cru : il se sert de ce qui est déjà présent dans l’application et dans les bibliothèques qu’elle utilise, en les enchaînant de façon que leur construction produise l’effet qu’il recherche. C’est pourquoi on parle de « chaîne de gadgets ».

C’est le défaut que le catalogue CWE appelle CWE-502. Il se corrige mal, pour une raison d’inventaire : pour interdire la reconstruction des objets dangereux, il faut connaître la liste de ceux qui sont légitimes, et presque aucune équipe ne la possède. La solution de fond consiste à ne plus transporter d’objets du tout, mais seulement des données (un nom, un numéro, une date), et à reconstruire soi-même, dans son propre code, ce qu’on en fait.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.