Situation actuelle : PHP2Shell. Deux failles PHP critiques (CVSS 9.2) permettent d'exécuter du code malveillant sans authentification. Correctifs disponibles depuis le 21 octobre 2026.Ce qu'il faut faire maintenant →

Désérialisation non sécurisée en PHP : unserialize et sécurité

unserialize() avec des données étrangères peut, dans le pire des cas, conduire à l'exécution de code. Comment le remplacer par JSON, le restreindre là où il doit rester et trouver les endroits à risque dans le code.

État 6 min de lectureGuides

En bref : pour la sécurité de unserialize en PHP, une règle simple s'applique : ne jamais traiter avec unserialize() des données venant de l'extérieur. Utilisez JSON à la place. Là où unserialize() doit rester à cause de code hérité, limitez-le avec allowed_classes => false et vérifiez l'authenticité des données par une signature.

Ce qui peut mal tourner avec unserialize()

PHP peut convertir des tableaux et des objets en chaîne de caractères avec serialize() et les restaurer avec unserialize(). C'est pratique pour stocker des données dans des cookies, des caches ou des champs de base de données. Le problème : unserialize() crée alors aussi des objets de n'importe quelle classe pouvant être chargée dans l'application.

À la création et, au plus tard, à la destruction d'un tel objet, PHP appelle automatiquement certaines méthodes, comme __wakeup(), __unserialize() ou __destruct(). Si des classes du projet ou de bibliothèques contiennent de telles méthodes avec des actions sur des fichiers, la base de données ou le système, un attaquant peut les détourner par des données manipulées. Les spécialistes parlent de PHP Object Injection. Selon les classes présentes, l'effet va de la suppression de fichiers isolés à l'exécution de code malveillant.

En résumé pour les non-techniciens : un seul appel irréfléchi au mauvais endroit peut laisser un attaquant pénétrer loin dans l'application. Cette vulnérabilité figure donc depuis des années parmi les sujets de l'OWASP Top 10.

L'alternative sûre : JSON au lieu de unserialize

Dans presque tous les cas où des données viennent de l'extérieur ou quittent le serveur, JSON est le bon choix. json_decode() ne crée que des tableaux, des nombres, des chaînes de caractères et des booléens, pas d'objets de classes personnalisées.

<?php
declare(strict_types=1);

function leseEinstellungen(string $eingabe): array
{
    try {
        $daten = json_decode($eingabe, true, 16, JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        throw new InvalidArgumentException('Ungültige Daten.');
    }

    if (!is_array($daten)) {
        throw new InvalidArgumentException('Erwartet wurde ein Objekt.');
    }

    // Ne reprendre que les champs connus avec les types attendus
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}

La seconde partie est importante : les données JSON sont, elles aussi, des entrées. Ne reprenez que les champs attendus et vérifiez les types et les plages de valeurs avant de les utiliser.

Quand unserialize est inévitable : allowed_classes et signature

Dans les anciennes applications, des données sérialisées se trouvent souvent dans des bases de données ou des caches qui ne peuvent pas être convertis immédiatement. Vous limitez alors le risque en deux étapes. Premièrement, vous empêchez avec allowed_classes la création d'objets. Deuxièmement, vous assurez avec une signature HMAC que les données proviennent bien de votre application et n'ont pas été modifiées en route.

<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // jamais dans le dépôt de code

function verpacken(array $daten): string
{
    $roh = serialize($daten);
    return hash_hmac('sha256', $roh, SCHLUESSEL) . '.' . base64_encode($roh);
}

function auspacken(string $paket): array
{
    [$signatur, $b64] = array_pad(explode('.', $paket, 2), 2, '');
    $roh = base64_decode($b64, true);

    if ($roh === false || !hash_equals(hash_hmac('sha256', $roh, SCHLUESSEL), $signatur)) {
        throw new RuntimeException('Signatur ungültig.');
    }

    $daten = unserialize($roh, ['allowed_classes' => false, 'max_depth' => 16]);
    if (!is_array($daten)) {
        throw new RuntimeException('Unerwartetes Format.');
    }
    return $daten;
}

Si certaines classes sont tout de même nécessaires, nommez-les une à une dans allowed_classes au lieu de toutes les autoriser. La clé doit se trouver dans la configuration du serveur ou dans un coffre à secrets et doit être renouvelée après un incident.

Convertir progressivement les données existantes en JSON

Dans les applications qui ont grandi, des valeurs sérialisées se trouvent souvent depuis des années dans la base de données. Une conversion en une seule étape est alors risquée. Une transition a fait ses preuves : l'application lit les deux formats, mais n'écrit plus que du JSON. À la lecture d'une ancienne valeur, elle est décompressée une fois avec allowed_classes => false et immédiatement réécrite en JSON. Au bout d'un certain temps, la plupart des entrées sont converties, et un script ponctuel s'occupe du reste.

Journalisez pendant la transition combien de fois l'ancien format est encore lu, ainsi que les cas où les signatures ne correspondent pas. De tels échecs peuvent indiquer des tentatives de manipulation et relèvent de la surveillance. Ce n'est que lorsque plus aucun ancien format n'apparaît que vous supprimez entièrement la branche unserialize() du code.

Désérialisation cachée : sessions, caches et chemins de fichiers

Toute désérialisation n'est pas visible dans votre propre code. Contrôlez aussi ces endroits :

  • Sessions : PHP sérialise automatiquement les données de session. Tant que des attaquants ne peuvent pas écrire les fichiers de session, ce n'est pas critique. Des chemins de session séparés par site sont décrits dans le guide Sécuriser les sessions en PHP.
  • Sérialiseur de session : l'avis sur CVE-2026-115560 concerne une configuration inhabituelle dans laquelle le serialize_handler est changé à l'exécution par ini_set() et où le suivi de progression des uploads de session est actif. Contournement selon php.net : session.upload_progress.enabled=0 dans php.ini ou le pool FPM, puis redémarrage des workers.
  • Caches et files d'attente : Redis, Memcached ou des tables de base de données contenant des valeurs sérialisées ne sont aussi sûrs que leur accès. Ils n'ont pas leur place ouverts sur le réseau.
  • Fonctions de fichiers avec des chemins issus de saisies : ne transmettez jamais à des fonctions de fichiers des chemins provenant de la requête. Des wrappers de flux comme phar:// peuvent sinon déclencher un traitement inattendu. Une liste d'autorisation de noms de fichiers protège ici plus sûrement que n'importe quel filtrage.
  • Dépendances : les bibliothèques aux vulnérabilités connues élargissent les possibilités d'un attaquant. Maintenez-les à jour, comme décrit dans le guide Vérifier les failles des dépendances Composer.

Trouver les endroits à risque dans le code

Un premier état des lieux se fait avec une recherche simple. Elle ne modifie rien dans le système et montre tous les appels en dehors du répertoire vendor :

grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'

Évaluez chaque résultat avec trois questions : d'où viennent les données ? Un utilisateur peut-il les modifier ? allowed_classes est-il défini ? Les résultats avec des entrées provenant de cookies, $_GET, $_POST, $_REQUEST ou d'interfaces externes sont prioritaires. Remplacez-les par JSON et testez la conversion avant de la déployer.

Un autre indice se trouve dans le navigateur. Les données PHP sérialisées ont une structure typique avec des identifiants de type et des indications de longueur. Si vous trouvez de telles valeurs dans des cookies, des champs de formulaire cachés ou des paramètres de votre propre site, l'application a remis des données sérialisées au visiteur et les attend vraisemblablement en retour. Ce sont précisément ces endroits qu'il faut examiner.

Pour la direction, une question simple aux développeurs ou à l'agence suffit : « Notre application traite-t-elle quelque part des données sérialisées venant de l'extérieur et, si oui, comment sont-elles protégées ? » Une réponse claire avec les endroits concernés est un bon signe. Une réponse évasive est une raison de faire examiner l'application.

Comment PHP SOS aide-t-il concrètement ?

  • Nous parcourons votre code à la recherche de désérialisation de données étrangères et évaluons chaque constat selon l'accessibilité et l'impact.
  • Nous proposons des conversions concrètes vers JSON ou des appels sécurisés et accompagnons vos développeurs dans la mise en œuvre.
  • Nous examinons les dépendances à la recherche de vulnérabilités connues et documentons l'état.
  • Sur demande, nous faisons examiner l'application de façon indépendante dans le cadre d'un audit de sécurité et test d'intrusion.

Questions fréquentes

unserialize() est-il fondamentalement peu sûr en PHP ?

Pas pour des données que seule votre propre application génère et que personne ne peut modifier. Cela devient dangereux dès que des utilisateurs ou des tiers peuvent influencer l'entrée, par exemple via des cookies, des champs de formulaire ou des interfaces externes.

allowed_classes => false suffit-il comme protection ?

Il empêche la création d'objets arbitraires et ferme ainsi la principale voie d'attaque. Pour les entrées venant de l'extérieur, JSON reste malgré tout le meilleur choix, car l'analyseur est plus simple et ne reconstruit pas d'objets.

Une mise à jour des bibliothèques aide-t-elle contre les attaques de désérialisation ?

En partie. Les attaques utilisent souvent des classes issues des dépendances qui exécutent des actions lorsqu'un objet est détruit ou réveillé. Les paquets à jour ferment les chaînes connues, mais la véritable cause reste l'appel à unserialize avec des données étrangères.

PHP2Shell a-t-il un rapport avec la désérialisation ?

Les deux failles principales se trouvent dans le traitement des requêtes de PHP. L'avis sur CVE-2026-115560 concerne en revanche une configuration inhabituelle de la sérialisation des sessions. Le contournement y est session.upload_progress.enabled=0.

Sources

Contact immédiat

Chaque heure compte. Écrivez-nous maintenant.

Décrivez brièvement votre système, votre version de PHP et ce que vous avez observé. Nous vous répondons, faisons le point et travaillons à distance jusqu'à la clôture et la documentation de l'incident.

Aide immédiate