# 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.

- Auteur : Clément Hadrot
- Publié le : 2022-08-17
- Mis à jour le : 2022-08-17
- Catégorie : Sécurité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/securite/faille-deserialisation-plugin-sauvegarde-cve/

## L’essentiel

- 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

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.
