Vulnérabilité PrestaShop ps_facetedsearch : analyse RCE via unserialize()

Examen technique de la faille RCE dans ps_facetedsearch via unserialize(): détection pragmatique, mitigations immédiates (allowed_classes=false, signature HMAC) et plan d’action pour la production.

Illustration numérique sur la sécurité PrestaShop avec focus sur unserialize() et RCE.

Table des matières :

  1. ps_facetedsearch : quand un module de facettes devient un problème de désérialisation
  2. RCE via injection d’objets : mécanique réelle et pourquoi PrestaShop augmente le risque
  3. Audit ciblé de ps_facetedsearch : repérer le “unserialize() dangereux” sans PoC offensif
  4. Indicateurs d’exploitation et détection : ce que vous pouvez observer sans fantasmer
  5. Correctifs réalistes : patch module, réduction de blast radius, et contraintes de prod
  6. Checklist opérationnelle : valider l’exposition, corriger, et éviter la récidive

ps_facetedsearch : quand un module de facettes devient un problème de désérialisation

ps_facetedsearch (successeur fonctionnel de l’ancien blocklayered) est au cœur de beaucoup de catalogues PrestaShop : génération de facettes, comptage des produits par filtre, URLs de navigation, et souvent mise en cache agressive pour éviter des requêtes SQL coûteuses. Sur des boutiques à gros catalogue, le module finit quasi systématiquement par stocker un état (sélection de filtres, paramètres, clés de cache, etc.) côté client (cookie, querystring) ou côté serveur (cache fichier/DB). C’est précisément là que les implémentations historiques dérapent : dès qu’un “état complexe” est stocké via serialize() et relu via unserialize(), vous avez une surface d’attaque.

Un point important pour cadrer le risque : l’état “facettes” est souvent traité comme un détail d’UX, alors qu’en réalité il touche à des zones très exposées (pages catégories, pages marque, pages recherche, AJAX sur listing). C’est typiquement l’endroit où :

  • les bots testent en boucle des paramètres (trafic “bruyant”, volumétrie élevée),
  • les équipes optimisent la perf (cache, cookies, pré-calcul),
  • et donc où l’on retrouve des “raccourcis” techniques (sérialisation PHP, absence de signature, validation limitée).

Le point clé n’est pas “unserialize() est interdit”, c’est “unserialize() sur une donnée non fiable est une bombe”. Le manuel PHP est explicite : « Do not pass untrusted user input to unserialize(). » (documentation officielle de unserialize(), PHP). Source : PHP manual — unserialize(). En PrestaShop, “donnée non fiable” inclut évidemment les paramètres HTTP, mais aussi les cookies (faciles à modifier), et même certaines valeurs en base si un attaquant a déjà obtenu un accès partiel (SQLi ailleurs, compte admin, module compromis).

Une vulnérabilité “RCE via unserialize()” dans ps_facetedsearch décrit généralement un scénario où le module désérialise un blob contrôlable par un attaquant, ce qui permet une injection d’objets PHP (PHP Object Injection). Selon le parc de classes chargées (core + modules), l’attaquant peut enchaîner sur une gadget chain (aussi appelée POP chain) et atteindre un effet critique : écriture de fichier, exécution de code, appel réseau sortant, etc. Et le point gênant : la criticité dépend moins de ps_facetedsearch que de votre base de code installée.

Mini-scénario (réaliste, sans PoC) : une boutique a activé des facettes “riches” (multi-sélection, AJAX), et pour éviter des recalculs, le module stocke l’état des filtres dans une valeur encodée (par exemple base64) transmise à chaque requête (paramètre d’URL ou cookie). Si cet état est désérialisé tel quel, un attaquant n’a pas besoin d’accéder au back-office : il lui suffit d’appeler une page listing publique avec un paramètre forgé, puis de répéter à grande échelle (bots). Sur un site e-commerce français ou européen fortement crawlé, ce type de vecteur peut être trouvé rapidement, simplement parce qu’il est “bruyant” et automatisable.

RCE via injection d’objets : mécanique réelle et pourquoi PrestaShop augmente le risque

La désérialisation PHP reconstruit des types primitifs (tableaux, strings…) mais peut aussi réinstancier des objets et déclencher des méthodes magiques (__wakeup, __destruct, __toString, __call, etc.). Une attaque de type PHP Object Injection consiste à faire désérialiser un objet d’une classe existante, avec des propriétés préparées pour provoquer un comportement dangereux lors d’un de ces “hooks” magiques. Ce n’est pas un “exploit monolithique” : c’est une composition de comportements légitimes de classes qui, une fois chaînés, devient un chemin vers l’impact.

Dans la pratique, une “gadget chain” exploitable finit souvent par atteindre une opération sensible (un “sink”) comme :

  • écriture/suppression de fichiers (cache, logs, templates),
  • appels réseau sortants (webhooks, clients HTTP),
  • exécution indirecte (appel d’un binaire, inclusion de template, chargement dynamique).

OWASP résume bien la classe de risque : la désérialisation de données non fiables peut mener à des impacts allant de la corruption de données jusqu’à l’exécution de code. Le cheat sheet OWASP est une référence utile pour cadrer la mitigation (signature, format sûr, filtrage, isolation) : OWASP Deserialization Cheat Sheet. L’intérêt ici n’est pas la théorie : c’est que la présence d’un seul unserialize() “faiblement contrôlé” dans une brique exposée (front) suffit à faire basculer la boutique.

PrestaShop amplifie ce risque pour trois raisons concrètes :

1) Autoloading massif : le core charge un grand nombre de classes, et en PrestaShop 8/9 vous avez en plus tout le monde Composer (Symfony components, libs tierces) dans certains contextes. Plus il y a de classes, plus vous augmentez la probabilité d’avoir une gadget chain exploitable.

2) Écosystème modules : chaque module rajoute des classes, parfois avec des __destruct qui font des opérations I/O (log, cache fichier, HTTP), parfois sans validation stricte des chemins/URLs. Vous ne maîtrisez pas la qualité du parc. C’est un point clé en audit : une boutique “standard” + 20 modules (tracking, marketplace, paiement, ERP, avis) n’a pas la même surface qu’une boutique minimaliste.

3) Contexte front exposé : ps_facetedsearch est appelé sur des pages catégories/listings, donc sur des routes publiques, souvent avec un trafic bot élevé. Sur ce type d’endpoint, une vulnérabilité est scannée et tentée en boucle (bruit dans les logs, pics de 4xx/5xx). Pour la partie “bots + perf”, vous pouvez recouper avec ce diagnostic TTFB : Diagnostic TTFB — Expertise PrestaShop.

Un angle “GEO / contexte UE” utile : une compromission e-commerce n’est pas seulement un sujet technique, c’est aussi un sujet opérationnel et réglementaire. Si la faille débouche sur exfiltration de données personnelles (comptes clients, adresses, historique), le RGPD impose une notification à l’autorité de contrôle dans certains cas. L’article 33 du RGPD précise notamment une fenêtre de 72 heures :

« …dans les meilleurs délais et, si possible, 72 heures au plus tard après en avoir pris connaissance… » (RGPD, Règlement (UE) 2016/679, art. 33)
Source (texte officiel) : Règlement (UE) 2016/679 — eur-lex.europa.eu

Cet aspect change la manière de gérer une suspicion RCE : il faut pouvoir prouver (ou réfuter) l’accès, dater, circonscrire et documenter — d’où l’importance des logs et d’un process d’incident.

Audit ciblé de ps_facetedsearch : repérer le “unserialize() dangereux” sans PoC offensif

Contexte de référence pour ce qui suit : PrestaShop 8.1/8.2/9.1 (2026), PHP 8.1 à 8.3, module ps_facetedsearch installé depuis le catalogue officiel. Pré-requis : accès au code (SFTP/SSH), environnement de staging fidèle (cf. Staging PrestaShop — Expertise PrestaShop), et sauvegarde avant toute modification (fichiers + base). N’allez pas patcher en prod “au feeling” : les facettes sont sensibles (cache, index, URLs SEO) et une régression peut tuer le crawl.

La méthode la plus fiable est bête et méchante : chercher toutes les occurrences de unserialize( dans le module et ses dépendances, puis tracer la provenance des données.

En pratique, démarrez par un inventaire reproductible (utile en équipe et en audit) :

# 1) Dans le module
grep -RIn --color "unserialize\s*(" modules/ps_facetedsearch

# 2) Dans les overrides éventuels (souvent oubliés)
grep -RIn --color "unserialize\s*(" override

# 3) Dans d'autres modules susceptibles d'être appelés sur le front (au besoin)
grep -RIn --color "unserialize\s*(" modules | head -n 200

Puis, pour chaque occurrence, remontez la chaîne :

  • la valeur vient-elle d’un cookie, d’un paramètre HTTP, d’un header (rare), d’un cache, ou d’une entrée DB ?
  • y a-t-il une étape de décodage (base64_decode, gzuncompress, urldecode) ?
  • existe-t-il un contrôle d’intégrité (signature/HMAC) avant la désérialisation ?
  • le code impose-t-il un type attendu (array de scalaires), avec rejet dur si non conforme ?

Deux cas reviennent en pratique :

  • désérialisation d’un champ provenant d’un cookie (état des filtres, préférences de tri, etc.) ;
  • désérialisation d’un champ provenant d’un paramètre GET/POST (souvent encodé ou compressé) ;
  • désérialisation d’un champ venant du cache (fichier/DB) mais dont la clé ou le contenu est indirectement contrôlable.

Le critère de vulnérabilité n’est pas “est-ce que ça vient directement de $_GET ?”. Le critère, c’est : est-ce que l’attaquant peut influencer le blob qui arrive dans unserialize() ? Exemple de pattern à bannir (forme générique) : récupération d’une valeur (cookie/param), base64_decode(), puis unserialize() sans intégrité, sans allowed_classes, et sans contrôle de type strict après coup.

Un moyen simple de prioriser (utile en revue de code) : classez chaque source selon le niveau de confiance et la mitigation attendue.

Source de données Peut être modifiée par un attaquant ? Risque typique Mitigation minimale
Paramètres GET/POST Oui Scans massifs, exploitation à distance Éviter unserialize, sinon allowed_classes=false + validation stricte + limite de taille
Cookie navigateur Oui Manipulation discrète, contournement de cache Signature (HMAC) + format non-objet (JSON)
Cache serveur (fichier/DB) Indirectement Poisoning via clés, collisions, injection amont Clés non contrôlables + format sûr + séparation des droits
Valeur DB “métier” Parfois Pivot après compromission partielle Validation et assainissement à l’entrée, pas seulement à la lecture

Si vous tombez sur un unserialize() nécessaire (compat legacy), la mitigation minimale en PHP moderne est d’utiliser le paramètre allowed_classes :

$data = @unserialize($raw, ['allowed_classes' => false]);
if (!is_array($data)) {
    $data = [];
}

Ce n’est pas “magique” mais ça coupe la réinstanciation d’objets (donc la PHP Object Injection), et ça force à traiter le résultat comme un tableau. Deux précisions utiles en audit :

  • utilisez une validation positive (liste de clés autorisées, types attendus) plutôt qu’un “nettoyage” vague ;
  • imposez des bornes (taille max du blob, profondeur max des tableaux) pour éviter les effets DoS.

Pour une mitigation saine, le vrai correctif est généralement de remplacer la sérialisation PHP par un format non objet (JSON) et d’ajouter une validation de schéma (types attendus, bornes) :

$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
// puis validation : clés attendues, arrays scalaires, etc.

Pour un cadre général (et parce que ça touche tous les modules, pas juste ps_facetedsearch), l’article dédié “Désérialisation PHP : bonnes pratiques…” est une base utile : Désérialisation PHP — Expertise PrestaShop.

Indicateurs d’exploitation et détection : ce que vous pouvez observer sans fantasmer

Sur une boutique exposée, une tentative de désérialisation malveillante laisse souvent des traces… mais pas toujours là où vous les cherchez. Côté HTTP, les patterns typiques sont des valeurs anormalement longues dans certains paramètres, une forte entropie (base64), ou des signatures de sérialisation PHP. Même si les données sont encodées, les sérialisations d’objets PHP contiennent souvent des marqueurs reconnaissables après décodage, du type O: (objet) ou C: (objet sérialisable) — ce qui permet des règles de détection côté WAF/IDS. L’objectif n’est pas de “bloquer à 100%”, c’est d’augmenter le coût de l’attaque et de réduire le temps de détection.

Concrètement, pour un premier niveau “pragmatique” côté logs (sans outil lourd), cherchez :

  • des paramètres contenant des fragments typiques (O:, C:, parfois a: pour les tableaux) après décodage si vous loggez la version décodée (souvent non, donc prudence) ;
  • des tailles de querystring qui explosent (par exemple des URLs de plusieurs kilo-octets sur des pages catégories) ;
  • des pics de 500 corrélés à des warnings du type unserialize(): Error at offset ... dans les error logs.

Exemple de requête de triage (Nginx access logs), pour isoler les URLs très longues (à adapter à votre format de logs) :

awk 'length($7) > 2000 {print $0}' /var/log/nginx/access.log | head

Côté serveur, si une RCE a été atteinte, les symptômes récurrents sur PrestaShop ressemblent rarement à “un shell visible”. C’est plutôt : nouveaux fichiers .php dans des répertoires inattendus (souvent img/, upload/, modules/<module>/), tâches cron ajoutées, connexions sortantes inhabituelles, et surtout modifications furtives dans des templates ou overrides. Si vous suspectez déjà une compromission (exfiltration carte, skimmer), l’article “Skimmer Magecart PrestaShop : détecter et nettoyer une infection” est pertinent pour la partie nettoyage/IOC : Skimmer Magecart — Expertise PrestaShop.

En forensics minimale, ne vous contentez pas des logs applicatifs : corrélez access logs, error logs PHP-FPM, et événements filesystem. Sur Debian/Ubuntu, activer auditd sur les répertoires sensibles (/var/www/.../modules, /themes, /override) donne des timelines exploitables. Sur Nginx, surveillez aussi les pics de 500 et les erreurs “unserialize(): Error at offset …” (souvent signe de sondage automatique). Enfin, si vous avez un cluster, comparez la dérive entre nœuds : une compromission réussie n’est pas forcément répliquée si vous n’avez pas de déploiement immutable.

Dernier point (souvent négligé en e-commerce) : si vous constatez des indices sérieux d’accès non autorisé, traitez immédiatement le volet “incident” (préservation des preuves, gel des logs, snapshots). En Europe, le facteur temps compte : la capacité à reconstituer une chronologie conditionne autant la remédiation technique que les obligations de notification (RGPD) et la relation avec vos partenaires (paiement, marketplaces, comparateurs).

Correctifs réalistes : patch module, réduction de blast radius, et contraintes de prod

Le correctif “propre” se traite comme une vulnérabilité critique : inventaire des versions, patch, déploiement contrôlé, puis rotation des secrets si un doute existe. Commencez par vérifier s’il existe une mise à jour de ps_facetedsearch (ou un correctif PrestaShop) via le canal officiel. Pour suivre les publications de sécurité côté core, les advisories GitHub sont un point d’entrée : PrestaShop security advisories — GitHub. Si votre modèle d’exploitation est “module via marketplace”, imposer un cycle de mises à jour outillé évite que le module reste gelé pendant 18 mois.

Avant même de patcher, sécurisez votre trajectoire de déploiement :

En attendant un correctif upstream, vous avez trois options défensives (du plus sûr au plus contraignant) :

1) Désactiver le module temporairement si le risque est critique et la boutique scannée. Ça casse l’UX filtres, mais c’est parfois le seul choix raisonnable. (À documenter côté SEO : baisse de pages indexables, changements d’URLs, impact sur conversion.)

2) Patch local minimal : neutraliser la réinstanciation d’objets (allowed_classes => false) + validation stricte du type/structure. Attention : si le module s’attend à des objets, ce patch peut introduire des bugs. En pratique, sur les états de filtres, on parle presque toujours de tableaux scalaires.

3) Migration vers JSON + signature : si l’état est stocké côté client, signez-le avec un HMAC (secret serveur) pour empêcher la modification. Sans intégrité, même du JSON reste altérable (et donc exploitable via logique applicative). C’est exactement le genre de patterns que vous devriez industrialiser au niveau transverse (core + modules), pas “au cas par cas”.

Exemple de pattern de signature (illustratif, défensif) : stocker payload (JSON) + sig (HMAC). À la lecture : recalculer la signature et refuser si mismatch, avant tout traitement.

$payload = $rawPayload; // JSON
$sig     = $rawSig;     // hex

$calc = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($calc, $sig)) {
    throw new RuntimeException('État facettes invalide (signature).');
}

$data = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);

Une fois patché, réduisez le blast radius. Un RCE dans PHP ne devient vraiment catastrophique que si le process a trop de droits. Visez : utilisateur PHP-FPM non privilégié, permissions strictes sur les répertoires d’écriture, séparation des volumes (code en lecture seule si possible), et base MySQL avec privilèges minimaux (utile si l’attaquant pivote vers exfiltration). Sur ce point, un rappel utile : Sécurité MySQL — Expertise PrestaShop.

Enfin, traitez la livraison logicielle sérieusement : un patch sécurité “fait à la main en prod” finit souvent par être écrasé au prochain déploiement. Si vous avez une chaîne CI/CD, vous devez versionner le patch (fork du module, overlay Git) et le tester. Les checklists de mise à jour PrestaShop sont un bon garde-fou sur sauvegarde, pré-prod, et rollback : Checklist mise à jour — Expertise PrestaShop.

Checklist opérationnelle : valider l’exposition, corriger, et éviter la récidive

Sur la partie exposition, arrêtez de raisonner “c’est juste un module de filtres”. ps_facetedsearch est un endpoint front très sollicité et intimement lié aux URLs de facettes (avec un impact SEO direct). Si vous devez couper ou modifier le comportement, prévoyez l’effet sur les URLs et le crawl ; l’article “Budget de crawl PrestaShop : maîtriser URLs de filtres et facettes” aide à cadrer le risque SEO et les mécanismes d’indexation : Budget de crawl — Expertise PrestaShop.

Checklist courte (orientée terrain) pour qualifier et traiter :

  • Qualifier l’exposition

  • [ ] Rechercher unserialize( dans modules/ps_facetedsearch + overrides.

  • [ ] Identifier la source exacte des données (GET/POST/cookie/cache/DB).

  • [ ] Mesurer la surface : quelles routes front utilisent ce code (catégories, recherche, AJAX) ?

  • [ ] Contrôler les limites (taille maximale acceptée, validation de structure, gestion d’erreurs).

  • Corriger sans casser

  • [ ] Patch minimal immédiat : allowed_classes => false + validation stricte (si applicable).

  • [ ] Plan de migration si changement de format (serialize → JSON) : lecture “double format” temporaire ou purge/invalidation coordonnée.

  • [ ] Tests front : pages catégories, combinaisons de filtres, pagination, tri, multi-boutiques si présent.

  • [ ] Tests de perf : TTFB, charge DB, efficacité du cache (avant/après).

  • Surveiller

  • [ ] Activer/renforcer la corrélation logs (Nginx + PHP-FPM + applicatif).

  • [ ] Alerter sur : hausse des 500, warnings unserialize, pics de longueur d’URL, patterns base64 suspects.

  • [ ] Vérifier l’intégrité du filesystem (diff de répertoires sensibles, date de modification des templates).

Sur la correction, faites une validation “développeur” plutôt qu’une validation “admin back-office”. Concrètement : tests sur pages catégories avec variations de filtres, vérification des caches, comparaison des SQL (profilage), et revue des logs PHP-FPM. Si vous modifiez un format de stockage (serialize → JSON), prévoyez une migration (compatibilité de lecture pendant une fenêtre) ou une invalidation (purge ciblée) pour éviter des erreurs aléatoires. Le cache natif et la base peuvent garder des états ; si vous devez purger proprement, recoupez avec : Optimisation PrestaShop — Expertise PrestaShop.

Sur la prévention, traitez le problème à la racine : politique “no unserialize on untrusted data” + SAST/grep en CI, plus des garde-fous runtime. L’article “PrestaShop : sécuriser APIs et modules avec SBOM, WAF et CI/CD” donne une approche structurée (inventaire dépendances, WAF, pipeline) applicable ici : Sécuriser APIs et modules — Expertise PrestaShop. Une boutique PrestaShop moderne, c’est un assemblage de modules : si vous ne savez pas ce qui tourne (SBOM) et comment c’est déployé (CI/CD), vous découvrirez les désérialisations dangereuses… le jour où elles sont exploitées.

Enfin, pensez “récidive” : ps_facetedsearch n’est souvent qu’un symptôme. Dès que vous avez trouvé un unserialize() contrôlable sur le front, élargissez l’audit aux autres modules exposés, en particulier ceux qui manipulent des états client (panier, comparateurs, tracking, API front). Une correction durable, c’est une convention d’architecture : formats non-objets, intégrité systématique côté client, et validation stricte côté serveur.



À lire aussi