WordPress stocke énormément de données structurées — les métadonnées d’articles, certaines options, le contenu des widgets classiques — sous une forme sérialisée : une chaîne de texte qui encode des tableaux ou des objets PHP de façon compacte et réversible. C’est serialize() qui produit cette chaîne, et unserialize() qui la reconstruit en structure de données utilisable. Ce mécanisme, ancien et largement répandu dans l’écosystème PHP, cache un risque de sécurité rarement expliqué en détail : la désérialisation d’une donnée dont le contenu est contrôlé par un utilisateur.
Cet article explique ce qu’est réellement ce risque, comment il se manifeste concrètement dans du code PHP, et pourquoi le format JSON constitue aujourd’hui l’alternative recommandée pour la quasi-totalité des besoins où une extension WordPress aurait autrefois utilisé la sérialisation native.
Définition : que fait réellement unserialize
La fonction unserialize() prend en entrée une chaîne au format de sérialisation PHP, comme a:2:{s:3:"nom";s:5:"Alice";s:3:"age";i:34;}, et reconstruit la structure de données d’origine — ici un tableau associatif. Rien de problématique en soi, tant que la chaîne provient d’une source de confiance, par exemple une valeur que le site lui-même a sérialisée plus tôt et stockée en base de données.
Le problème apparaît quand la chaîne à désérialiser provient, même indirectement, d’un utilisateur non authentifié : un paramètre d’URL, un champ de formulaire, un cookie. Dans ce cas, l’utilisateur ne fournit pas seulement des données, il fournit la structure même de l’objet qui sera reconstruit côté serveur.
Fonctionnement interne : pourquoi c’est dangereux
PHP permet de définir des méthodes dites « magiques » sur une classe, comme __wakeup() ou __destruct(), automatiquement appelées à certains moments du cycle de vie d’un objet — respectivement juste après sa désérialisation, et juste avant sa destruction. Si une classe chargée par l’application définit une de ces méthodes avec un comportement sensible (suppression de fichier, exécution de commande, écriture en base), et qu’un attaquant parvient à faire désérialiser un objet de cette classe avec des propriétés qu’il contrôle, il peut déclencher ce comportement sans jamais appeler la méthode directement.
Cette technique s’appelle une chaîne d’exploitation par objets, ou POP chain (Property-Oriented Programming). Elle exige généralement la présence d’une classe « gadget » vulnérable quelque part dans le code chargé (cœur, extension ou thème), mais l’écosystème WordPress, avec des milliers d’extensions installées en combinaison sur un même site, offre statistiquement plus d’occasions qu’un projet isolé.

Ce que fait maybe_unserialize et ses limites
WordPress fournit maybe_unserialize(), utilisée en interne notamment pour les métadonnées, qui vérifie d’abord si la chaîne fournie ressemble à une donnée sérialisée avant d’appeler unserialize() dessus, et retourne la valeur telle quelle sinon :
$valeur = maybe_unserialize( $donnee_stockee );
Cette fonction protège contre les erreurs de traitement d’une chaîne qui n’est pas réellement sérialisée, mais elle ne constitue pas une protection contre l’injection d’objets : si la chaîne fournie est effectivement une sérialisation PHP valide, même malveillante, maybe_unserialize() la désérialisera tout de même. Elle ne remplace donc pas une réflexion sur l’origine de confiance de la donnée traitée.
Cas d’usage : privilégier JSON pour les données externes
Pour toute donnée destinée à transiter par une source externe — paramètre d’URL, corps de requête API, valeur d’un cookie personnalisé — le format JSON, décodé avec les fonctions natives json_decode() et encodé avec json_encode(), ne présente pas ce risque : le décodage JSON ne reconstruit jamais d’objet PHP arbitraire par défaut, seulement des types de données simples (chaînes, nombres, tableaux, valeurs booléennes).
$donnees = json_decode( wp_unslash( $_POST['payload'] ?? '' ), true );
if ( ! is_array( $donnees ) ) {
wp_die( __( 'Format de données invalide.', 'mon-extension' ) );
}
Cette bascule ne demande généralement pas de refonte majeure : remplacer un point d’entrée qui accepte une chaîne sérialisée fournie par le client par un point d’entrée qui accepte du JSON couvre la très large majorité des besoins d’échange de données structurées côté client.
Pièges à connaître
- Un cookie personnalisé stockant une valeur sérialisée reste une cible fréquente, car les cookies sont entièrement sous le contrôle du navigateur du visiteur.
- Certaines extensions de migration ou d’import/export échangent encore des données au format sérialisé PHP par habitude historique, sans que ce format soit réellement nécessaire.
- La présence d’une classe avec une méthode
__destruct()ou__wakeup()sensible n’est dangereuse qu’en combinaison avec un point de désérialisation exploitable ; corriger l’un des deux suffit à neutraliser la chaîne complète.
Désérialiser une chaîne fournie par un visiteur, c’est lui laisser décider quelle classe PHP votre application va instancier — rarement une bonne idée.
En résumé
L’injection d’objets PHP via unserialize() reste une classe de vulnérabilité méconnue mais bien documentée dans l’écosystème PHP au sens large, WordPress compris. Réserver la sérialisation native aux données strictement internes au site, et adopter JSON pour tout échange avec une source externe, élimine ce risque à la racine sans complexifier le code de façon notable.