Durcissement avancé de PrestaShop pour les boutiques qu'on ne peut pas encore mettre à niveau
Dans notre analyse forensique d'une attaque de type Magecart, nous avons avancé une idée qui, avec le recul, méritait son propre article : nettoyer le malware d'une boutique n'équivaut pas à réparer la boutique. Nous avons retiré le skimmer, les web shells et les binaires SUID-root de cette boutique PrestaShop 1.7.x — et peu après, elle a été touchée de nouveau. Pas par une porte dérobée oubliée, mais par la porte d'entrée : une vulnérabilité applicative que le nettoyage avait contenue sans jamais la refermer. Cet article est le manuel que nous aurions aimé appliquer la première fois — comment durcir une boutique PrestaShop qu'on ne peut pas encore mettre à niveau, pour que retirer la charge utile referme aussi la brèche par laquelle elle est entrée.
Si vous pouvez passer à PrestaShop 8 ou 9 aujourd'hui, faites-le — c'est la seule correction durable, et le reste de cet article n'est qu'un palliatif. Mais pour une boutique non triviale, une mise à niveau est un projet de plusieurs semaines (compatibilité des modules, refactorisation du thème, recâblage des paiements). L'intérim est réel, et pendant celui-ci vous êtes une cible. Voici comment le rendre survivable. C'est le complément avancé de notre checklist de durcissement PrestaShop ; commencez là pour les bases, venez ici pour la couche « incident ».
1. Trouvez le vecteur d'entrée, pas seulement la charge utile
L'erreur la plus coûteuse en réponse à incident est de confondre le symptôme visible avec la plaie. Un skimmer dans le JavaScript du thème est le symptôme. La plaie, c'est ce qui a permis à un attaquant d'écrire ce JavaScript — un web shell, un module non corrigé, un point d'injection. Retirez le skimmer en laissant la plaie ouverte, et vous avez gagné des jours, pas de la sécurité.
Avant de durcir quoi que ce soit, répondez à une question avec des preuves : comment du code a-t-il été écrit sur le disque ? Lisez les journaux d'accès autour des horodatages de création des fichiers malveillants (utilisez ctime, pas mtime — les attaquants falsifient mtime). Corrélez les motifs POST suspects, les paramètres de requête inhabituels et les anomalies de taille de réponse avec le moment d'apparition de la charge utile. Si vous ne pouvez pas nommer le vecteur d'entrée, supposez qu'il est toujours ouvert.
2. Appliquez des « correctifs virtuels » à la chaîne d'attaque 1.7.x connue
PrestaShop 1.7.x compte une poignée de vulnérabilités bien connues et armées que les scanners automatisés enchaînent. Si vous ne pouvez pas mettre à niveau, corrigez directement les chemins de code concernés. Ce sont des palliatifs — suivez les avis en amont — mais ils referment les portes activement enfoncées.
2.1 Injection SQL dans la recherche à facettes
Les anciennes versions de ps_facetedsearch concatènent les valeurs de filtre directement dans le SQL. C'est une primitive d'injection SQL en aveugle, et sur une boutique 1.7.x c'est souvent le véritable point d'entrée. Échappez et castez chaque valeur issue de la requête :
// BEFORE (vulnerable): raw value concatenated into the WHERE clause
$conditions[] = $alias.'.'.$field.$operator.current($values);
// AFTER: string values escaped, numeric ranges cast
$conditions[] = $alias.'.'.$field.$operator."'".pSQL(current($values))."'";
$orConditions[] = $alias.'.'.$field.$operator.(float) $value;
2.2 La RCE du cache MySQL de Smarty (CVE-2022-36408)
C'est la moitié de la chaîne qui transforme un accès base de données en exécution de code : le cache de templates Smarty adossé à la base peut être contraint de rendre du PHP contrôlé par l'attaquant. La plupart des boutiques ne l'utilisent jamais. Désactivez-le totalement dans config/smarty.config.inc.php pour qu'il soit inatteignable :
// AFTER: the MySQL cache include is permanently switched off
if (false /* CVE-2022-36408 mitigation: MySQL Smarty cache disabled */) {
include _PS_CLASS_DIR_.'Smarty/SmartyCacheResourceMysql.php';
$smarty->caching_type = 'mysql';
}
Confirmez ensuite que la boutique utilise le cache fichier (PS_SMARTY_CACHING_TYPE = filesystem). Une injection SQL seule est grave ; une injection SQL capable d'écrire un template PHP dans un cache et de l'exécuter, c'est une prise de contrôle totale.
2.3 Modules abandonnés (RevSlider et consorts)
Les vieux modules non maintenus sont un vecteur récurrent. Si vous ne pouvez pas retirer un module parce que le thème en dépend, interdisez l'exécution web directe de ses scripts. Le rendu front-end passe par les hooks PrestaShop côté serveur, pas par l'appel direct des fichiers PHP du module :
# modules/<module>/.htaccess
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
Et verrouillez les fichiers d'entrée statiques que l'attaquant re-cible après chaque nettoyage. Sur les systèmes de fichiers honorant les attributs étendus (ext4/xfs/btrfs) : chattr +i index.php config/*.php. Un futur shell ne pourra pas écraser un fichier immuable sans d'abord exécuter chattr -i, ce qui requiert root.
3. Verrouillez le runtime PHP — correctement
Désactiver les fonctions qu'un shell utilise pour lancer des processus système en vaut la peine, mais c'est l'étape le plus souvent mal faite — d'une manière qui semble appliquée sans l'être.
disable_functions = exec,system,shell_exec,passthru,popen,proc_open,
proc_close,proc_get_status,proc_terminate,dl,show_source,phpinfo
Trois choses qu'on oublie :
disable_functionsest de typePHP_INI_SYSTEM. Il n'est appliqué que depuis lephp.inide démarrage du SAPI qui sert réellement le site. Sur une machine gérée par panel, le SAPI web est souvent un pool PHP-FPM précis ou une instance Apache propre à une version — pas le PHP CLI système. Éditez le bonphp.iniet redémarrez exactement ce service, sinon votre modification ne fait rien.ini_get('disable_functions')ment. Il peut rapporter une liste de blocage complète qui n'est pas réellement appliquée. Le seul test fiable est un test réel, exécuté via le web : un script temporaire (nom aléatoire, supprimé immédiatement) qui appellefunction_exists('exec')et tente@exec('id'), récupéré en HTTP. Fiez-vous au résultat de la tentative, pas à la configuration rapportée.- Ce n'est pas un coupe-circuit. Il empêche un shell PHP de lancer des commandes système. Il ne neutralise pas un shell PHP en
eval— celui-ci peut toujours lire et écrire des fichiers dansopen_basediret dialoguer avec la base via les identifiants deparameters.php. Traitez-le comme une couche, pas comme la réponse.
Le cœur de PrestaShop utilise exec() dans quelques chemins image/MIME qui disposent de replis finfo, donc le désactiver ne casse pas les uploads — testez tout de même les envois d'images en staging avant la mise en production.
4. Placez Cloudflare devant — comme couche de correctif virtuel
Un WAF en reverse-proxy est ce qui se rapproche le plus d'un correctif virtuel déployable en quelques minutes. Mais cela ne fonctionne que si les attaquants ne peuvent pas le contourner.
4.1 Verrouillez l'origine sur Cloudflare
Un WAF devant une origine qui répond encore sur son IP publique est optionnel du point de vue de l'attaquant. Restreignez les ports web du serveur pour que seul Cloudflare puisse les atteindre :
# allow 80/443 only from Cloudflare's published ranges, drop the rest
iptables -N CF_WEB
iptables -A CF_WEB -s 173.245.48.0/20 -j ACCEPT # × Cloudflare's IPv4 ranges
# (+ the IPv6 ranges, + 127.0.0.1)
iptables -A CF_WEB -j DROP
iptables -I INPUT -p tcp -m multiport --dports 80,443 -j CF_WEB
Le piège que la plupart oublient : votre DNS de messagerie révèle l'IP de l'origine. Un enregistrement MX, un enregistrement SPF ou un sous-domaine mail. non proxifié livre la véritable adresse à l'attaquant — et c'est le pare-feu ci-dessus qui rend cette fuite inoffensive pour le web. Proxifiez chaque enregistrement web via Cloudflare, et protégez l'origine par pare-feu quoi qu'il arrive. Laissez SSH, la messagerie et vos ports de panel sur leurs propres règles d'accès — le verrouillage web ne doit jamais y toucher.
4.2 Règles WAF personnalisées — et le piège des extensions
Sur le plan Free de Cloudflare, vous avez cinq règles personnalisées et aucune expression régulière (seulement contains, eq, starts_with, ends_with, lower()). C'est suffisant pour un jeu de règles ciblé :
- Bloquer web shells et uploads exécutables : noms de shells connus, appels directs au PHP de modules abandonnés, et exécution PHP sous les répertoires d'assets (
/img/,/upload/,/themes/,/js/,/cache/). - Bloquer les charges d'injection :
../,/etc/passwd,union select,information_schema,<?php,base64_decodeet les variantes encodées. - Bloquer les fichiers sensibles :
/.git/,/.env,composer.json, vos fichiers de configuration. - Bloquer les pires crawlers nuisibles par user-agent.
Le piège : Apache exécute souvent plus que .php. Une ligne AddType typique mappe .php .php3 .php4 .phtml vers le handler PHP. Une règle qui bloque .phtml/.phar/.php5 mais oublie .php3 et .php4 laisse une porte d'upload de shell ouverte — nous avons trouvé exactement cette faille dans notre propre jeu de règles lors d'une revue. Bloquez chaque extension que le serveur exécutera réellement, et vérifiez votre configuration AddType/AddHandler réelle plutôt que de supposer.
4.3 Limitation de débit : sachez ce que votre plan peut et ne peut pas faire
La limitation de débit est l'outil adapté contre les déferlantes de crawlers qui matraquent la recherche à facettes (nous avons traité cet angle DoS séparément). Mais soyez précis sur votre offre. Sur le plan Free de Cloudflare, l'unique règle de limitation ne peut matcher que sur le chemin de l'URL — pas la chaîne de requête, pas le user-agent, pas l'IP source — son action se limite à block, et la fenêtre est fixe. Cela signifie que vous ne pouvez pas construire une règle « limiter les bots sur les URL à facettes ?q= » sur Free ; la chaîne de requête lui est invisible. Le mieux possible est une limite de déferlante par IP sur les chemins non statiques. Une vraie limitation tenant compte de la requête et du user-agent (avec un challenge managé plutôt qu'un blocage dur) nécessite l'Advanced Rate Limiting du plan Pro. Décidez si l'exposition de la recherche à facettes justifie la mise à niveau — pour une boutique activement ciblée, c'est souvent le cas.
5. Ajoutez de la détection — pour repérer le second round en quelques heures
Le durcissement réduit les probabilités ; il ne vous rend pas immunisé. La différence entre un mauvais après-midi et un incident à déclaration obligatoire, c'est la vitesse à laquelle vous remarquez. La boutique de notre étude de cas a skimmé en silence près de deux jours avant que quiconque ne regarde. Gardez un œil dessus :
- Surveillance d'intégrité des fichiers (AIDE, Wazuh, OSSEC, ou un simple cron à manifeste hashé) sur le webroot, alertant sur tout PHP nouveau ou modifié hors d'un déploiement.
- Balayages IOC planifiés pour les motifs d'attaques réelles — voir les indicateurs dans notre article d'anatomie — plus tout nouveau fichier à extension exécutable apparaissant dans les répertoires d'assets ou d'upload.
- Revue des Security Events Cloudflare selon un calendrier, pas seulement après un incident. Votre WAF est aussi un capteur.
6. Le remède reste la migration
Tout ce qui précède est de la défense en profondeur sur un logiciel qui ne reçoit plus de mises à jour de sécurité. Cela augmente le coût de l'attaque, referme les portes activement exploitées et vous achète les semaines ou mois qu'exige une vraie migration — mais cela achète du temps, pas de la sécurité. La boutique de notre étude de cas en est la preuve : elle a été nettoyée, et le même opérateur est revenu par une faille applicative que le nettoyage n'avait pas corrigée. Planifiez le passage à PrestaShop 8 ou 9, sur une infrastructure propre, avec rotation de tous les identifiants, comme la véritable remédiation. Considérez le durcissement décrit ici comme ce qui maintient les lumières allumées jusque-là.
Pour les pièces connexes : la checklist de durcissement pour les bases, le durcissement admin/2FA et les règles .htaccess pour les points d'entrée, ainsi que notre guide de réponse à incident pour quand le pire est déjà arrivé. Et l'anatomie de l'attaque qui a motivé chacune des étapes ci-dessus.
Commentaires
Aucun commentaire pour le moment. Soyez le premier !
Soyez le premier à poser une question ou à partager un retour utile.
Laisser un commentaire
Partagez une question, un détail de pose ou un retour qui pourrait aider un autre lecteur.