vendredi 25 septembre 2026

À propos

Contact

Sécurité

Une faille de désérialisation dans un plugin de sauvegarde : le CVE analysé

Analyse d'un avis de vulnérabilité réel touchant un plugin de sauvegarde populaire, où un objet PHP désérialisé sans contrôle permettait l'exécution de code.

Par Clément Hadrot • 17 août 2022 • 6 min de lecture • Aucun commentaire
Une faille de désérialisation dans un plugin de sauvegarde : le CVE analysé

Un avis de sécurité publié sur une base de vulnérabilités WordPress catalogue une faille touchant une extension de sauvegarde installée sur plusieurs millions de sites : une fonctionnalité de restauration à partir d’un fichier de configuration exporté acceptait, dans un de ses champs, une chaîne sérialisée PHP directement passée à unserialize() sans aucune validation de son origine ni de son contenu. Le score CVSS attribué, 7,5, reflète une gravité élevée : la faille permettait, sous certaines conditions, l’exécution de code arbitraire sur le serveur.

Ce cas mérite d’être décortiqué non pas pour blâmer les auteurs de l’extension concernée — ce type d’erreur est resté fréquent pendant des années dans l’écosystème WordPress avant que la vigilance collective ne progresse — mais parce qu’il illustre un mécanisme d’attaque, la désérialisation d’objets PHP, que tout développeur d’extension doit savoir reconnaître dans son propre code.

Comprendre la sérialisation PHP et son risque

serialize() transforme une structure de données PHP (tableau, objet) en une chaîne de caractères représentant fidèlement son contenu et son type, afin de pouvoir la stocker ou la transmettre puis la reconstituer plus tard avec unserialize(). WordPress utilise abondamment ce mécanisme, notamment pour stocker des tableaux d’options complexes dans wp_options.

Le danger apparaît lorsque unserialize() reçoit une chaîne dont le contenu n’est pas maîtrisé, en particulier si cette chaîne représente un objet d’une classe existant dans le code chargé (celui de WordPress, d’une extension ou d’une bibliothèque tierce). PHP reconstitue alors un véritable objet de cette classe et, ce faisant, appelle automatiquement certaines méthodes magiques si elles existent : __wakeup() à la reconstruction, ou __destruct() quand l’objet est détruit en fin de script. Si l’une de ces méthodes, dans une classe quelconque disponible sur le site, effectue une action dangereuse (écriture de fichier, exécution de commande, requête SQL brute) à partir de propriétés de l’objet, un attaquant qui contrôle la chaîne sérialisée contrôle également ces propriétés — et donc, potentiellement, l’action déclenchée.

Le mécanisme exact retrouvé dans l’extension analysée

L'essentiel à retenir : unserialize sur une entrée utilisateur ouvre la porte aux chaînes POP ; Une classe avec __wakeup ou __destruct devient une arme ; maybe_unserialize n'est pas une protection contre ce risque

L’avis de vulnérabilité et les correctifs publiés permettent de reconstituer le schéma simplifié de la faille :

// Fonction de restauration de configuration, simplifiée pour l'illustration
function moncpt_restaurer_configuration( $donnees_import ) {
    $configuration = unserialize( $donnees_import ); // aucune validation en amont

    if ( $configuration instanceof MonCpt_Parametres ) {
        $configuration->appliquer();
    }
}

La classe MonCpt_Parametres, définie ailleurs dans l’extension, contenait une méthode __destruct() qui écrivait le contenu d’une de ses propriétés dans un fichier, à un chemin lui-même dérivé d’une autre propriété de l’objet — un mécanisme légitime de journalisation interne, jamais pensé pour recevoir des données non fiables. En construisant une chaîne sérialisée représentant un objet MonCpt_Parametres avec un chemin de fichier arbitraire (par exemple un fichier .php dans un dossier accessible publiquement) et un contenu correspondant à du code PHP exécutable, un attaquant transformait ce mécanisme de journalisation anodin en une primitive d’écriture de fichier arbitraire — l’étape clé pour déposer un webshell exploitable ensuite via une simple requête HTTP.

Cette technique, connue sous le nom de chaîne de « ROP » ou plus précisément de « POP chain » (Property-Oriented Programming) dans le contexte PHP, ne nécessite pas de trouver une faille dans la classe visée elle-même : elle exploite l’enchaînement de méthodes magiques déjà présentes, souvent à des fins parfaitement légitimes, pour produire un effet que leurs auteurs n’avaient jamais anticipé.

Pourquoi maybe_unserialize ne protège de rien ici

Certains développeurs, en découvrant ce type de faille, pensent à tort que remplacer unserialize() par la fonction WordPress maybe_unserialize() apporte une protection. Ce n’est pas le cas : cette fonction vérifie seulement si la chaîne fournie ressemble à une donnée sérialisée avant d’appeler unserialize() en interne si c’est le cas ; elle ne filtre absolument pas le contenu ni le type d’objet reconstruit. Utiliser maybe_unserialize() sur une entrée non fiable reproduit exactement la même vulnérabilité.

Le correctif appliqué et le principe à retenir

La correction publiée par les auteurs de l’extension a consisté à remplacer le format d’échange par du JSON, avec json_decode(), qui ne reconstruit jamais d’objet PHP arbitraire par défaut (il produit des tableaux ou des objets génériques stdClass, sans méthodes magiques associées) :

function moncpt_restaurer_configuration_corrige( $donnees_import ) {
    $configuration = json_decode( $donnees_import, true );

    if ( ! is_array( $configuration ) || ! isset( $configuration['version'] ) ) {
        return new WP_Error( 'configuration_invalide', 'Format de configuration invalide.' );
    }

    // Reconstruction contrôlée de l'objet à partir de champs validés un par un,
    // jamais par reconstruction automatique d'une classe arbitraire.
    $parametres = new MonCpt_Parametres();
    $parametres->definir_depuis_tableau( $configuration );
    $parametres->appliquer();
}

Le principe général à retenir dépasse ce plugin précis : toute donnée sérialisée au format PHP qui provient d’une source extérieure (upload, requête HTTP, import) ne doit jamais être passée à unserialize() sans un contrôle strict, idéalement en évitant complètement ce format d’échange au profit de JSON, qui ne présente pas cette classe de risque par construction.

Sur nos revues de code d’extensions tierces, toute occurrence de unserialize( déclenche une vérification immédiate de l’origine de la donnée passée en argument : si elle provient, même indirectement, d’une requête utilisateur, la revue s’arrête là jusqu’à correction.

Ce que cette analyse ne couvre pas

Cet article se concentre sur la mécanique de la faille telle que documentée dans l’avis public, pas sur une explication exhaustive de unserialize() et de ses options de sécurité (comme l’argument allowed_classes, qui permet de restreindre les classes reconstructibles si l’usage de cette fonction reste malgré tout nécessaire), un point technique qui mériterait un traitement dédié à part entière.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi