Vous avez passé le week-end dessus. Vous avez comparé les fichiers du cœur avec une archive officielle, supprimé les trois .php qui n’avaient rien à faire dans /img/, changé tous les mots de passe, forcé la déconnexion des sessions. Lundi matin, le site est propre. Vous vérifiez le code source du front-office : impeccable.
Mercredi, un client appelle. Sa carte a été débitée deux fois, dont une sur un site qu’il ne connaît pas.
Vous rouvrez le front-office. Le script est revenu.
Si cette scène vous parle, le problème n’est pas votre nettoyage. Le problème est que vous avez nettoyé le mauvais endroit.
Le malentendu de départ
Quand on parle de site piraté, tout le monde pense fichiers. C’est logique : c’est là qu’on voit les dégâts, c’est ce que scannent la plupart des outils, et c’est ce qu’on sait comparer à une version saine.
Mais un attaquant qui a passé du temps sur votre boutique sait parfaitement que vous allez nettoyer les fichiers. C’est même l’hypothèse de travail. Alors il place sa porte de sortie ailleurs — dans le seul endroit que personne ne compare jamais à une référence, parce qu’il n’en existe pas : votre base de données.
Votre base contient vos commandes, vos clients, vos produits. Vous ne pouvez pas la « restaurer depuis une version propre » sans perdre votre activité. L’attaquant le sait. Vous ne toucherez pas à sa cachette.
Et ce n’est pas de la théorie : la persistance en base est aujourd’hui le mode opératoire standard sur les compromissions PrestaShop un peu sérieuses. Les campagnes de vol de carte de ces deux dernières années — celles qui exfiltrent vers des domaines comme ccfresh.co, jscode.cloud ou des WebSocket en wsocket.store — reposent presque toutes sur une réinjection depuis la base.
Voici les six cachettes, dans l’ordre où je vous conseille de les inspecter. Pour chacune, une requête que vous pouvez lancer tout de suite dans phpMyAdmin ou Adminer.
Une précision avant de commencer : je pars du préfixe de table par défaut
ps_. Le vôtre est peut-être différent — vérifiez la valeur de$_DB_PREFIX_dansapp/config/parameters.php(PrestaShop 1.7 et suivants) ouconfig/settings.inc.php(1.6), et adaptez les requêtes.
1. ps_configuration — la cachette numéro un
PrestaShop propose des champs de code personnalisé, injectés dans chaque page servie. C’est une fonctionnalité légitime, très pratique pour poser un tag analytics. C’est aussi la meilleure planque du web e-commerce.
SELECT id_configuration, name, LEFT(value, 300) AS extrait
FROM ps_configuration
WHERE value REGEXP '<script|<iframe|eval\\(|atob\\(|fromCharCode|document\\.write|\\.src[[:space:]]*='
ORDER BY id_configuration DESC;
Vous allez remonter des faux positifs — votre tag Google Analytics, votre pixel Meta, un chat en ligne. Ne supprimez rien mécaniquement. Regardez chaque ligne et posez une seule question : est-ce que je reconnais ce domaine ?
Le piège, c’est que le code malveillant est presque toujours obfusqué. Vous ne verrez pas https://voleur.com/steal.js en clair. Vous verrez quelque chose comme :
var _0x = atob('aHR0cHM6Ly9leGVtcGxlLW1hbHZlaWxsYW50LnRsZC9hLmpz');
Ce atob() décode du base64. Prenez la chaîne entre guillemets et décodez-la — en ligne, ou plus simplement en console navigateur avec atob("..."). Vous obtiendrez l’URL réelle. Une variante plus retorse utilise String.fromCharCode() avec un tableau de nombres, parfois passés par un XOR pour que même le décodage naïf ne donne rien de lisible.
Retenez le principe : du code encodé dans un champ de configuration n’a aucune raison d’être là. Aucun outil légitime n’a besoin de cacher son URL.
2. Le module désactivé qui tourne quand même
Celui-ci mérite qu’on s’arrête, parce qu’il trompe absolument tout le monde — y compris des développeurs expérimentés.
Dans PrestaShop, l’état d’un module et ses accroches aux hooks vivent dans deux tables différentes. ps_module dit si le module est actif. ps_hook_module dit à quels hooks il est accroché. Désactiver un module met active = 0 dans la première table, mais ne le décroche pas de la seconde.
Résultat : un module qui apparaît désactivé dans votre back-office, absent de la liste des modules actifs, invisible dans toutes vos vérifications de routine — et qui s’exécute pourtant à chaque affichage de page.
SELECT m.id_module, m.name, h.name AS hook, m.active
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 m.active = 0
ORDER BY m.name;
Toute ligne qui remonte ici mérite une explication. Un module désactivé mais toujours accroché, c’est au mieux un résidu d’installation bâclée, au pire votre ré-infection.
Vérifiez aussi les modules dont le nom ne vous dit rien. Les attaquants choisissent des noms crédibles — quelque chose comme gridhtml, blockcache ou psstats, qui ressemblent à s’y méprendre à des modules officiels. Si vous ne vous souvenez pas de l’avoir installé, allez voir le dossier correspondant dans /modules/.
3. Les tâches cron
Si le module Cronjobs est installé, il y a une table à regarder :
SELECT id_cronjob, description, task, active, hour, day
FROM ps_cronjobs
ORDER BY id_cronjob DESC;
Ce que vous cherchez : une tâche qui télécharge quelque chose. curl, wget, file_get_contents sur une URL externe, ou une tâche pointant vers un fichier PHP que vous ne reconnaissez pas.
C’est le mécanisme le plus frustrant, parce qu’il rend votre nettoyage littéralement inutile. Vous supprimez les fichiers infectés à 14 h, la tâche tourne à 15 h, elle les recrée. Vous recommencez le lendemain. Vous concluez que votre hébergeur a un problème.
Pensez aussi aux crons hors PrestaShop : le planificateur de votre hébergeur (cPanel, Plesk) et la crontab système si vous êtes sur un VPS. Un crontab -l sur le serveur prend cinq secondes et évite parfois trois jours d’errance.
4. Les comptes administrateurs
SELECT id_employee, email, firstname, lastname, id_profile, active, last_connection_date
FROM ps_employee
ORDER BY id_employee DESC;
Regardez la liste entière, pas seulement les dernières lignes. Un compte créé par un attaquant n’a aucune raison d’avoir un identifiant élevé s’il a pris la peine de le maquiller.
Ce qui doit vous alerter : une adresse e-mail que vous ne reconnaissez pas, un compte avec id_profile = 1 (SuperAdmin) que vous n’avez pas créé, un ancien salarié toujours actif, ou un compte qui ne s’est jamais connecté — typique d’un accès de secours gardé au chaud.
Tant qu’un compte pirate existe, changer votre propre mot de passe ne sert à rien.
Dans le même esprit, jetez un œil aux onglets d’administration, où l’on trouve parfois un contrôleur ajouté à la main :
SELECT id_tab, class_name, module, active
FROM ps_tab
WHERE module IS NOT NULL AND module <> ''
ORDER BY id_tab DESC;
5. Les contenus éditoriaux
Pages CMS, descriptions produits, balises meta, descriptions de catégories : tout champ qui accepte du HTML est un vecteur.
SELECT 'cms' AS source, id_cms AS id, LEFT(content, 200) AS extrait
FROM ps_cms_lang
WHERE content REGEXP '<script|<iframe|eval\\(|atob\\('
UNION ALL
SELECT 'produit', id_product, LEFT(description, 200)
FROM ps_product_lang
WHERE description REGEXP '<script|<iframe|eval\\(|atob\\('
UNION ALL
SELECT 'meta', id_meta, LEFT(description, 200)
FROM ps_meta_lang
WHERE description REGEXP '<script|<iframe';
L’injection dans une fiche produit obscure est particulièrement pénible à détecter : la page n’est visitée par personne, vous ne la contrôlez jamais, et Google la crawle quand même. C’est souvent par là que passe le référencement parasite — celui qui fait apparaître votre boutique dans des résultats de recherche pour des produits que vous ne vendez pas.
6. Le domaine de la boutique
Le plus rapide à vérifier, et il vaut le coup d’œil :
SELECT id_shop, domain, domain_ssl, physical_uri FROM ps_shop_url;
SELECT name, value FROM ps_configuration
WHERE name IN ('PS_SHOP_DOMAIN', 'PS_SHOP_DOMAIN_SSL');
Ces valeurs doivent être identiques et correspondre à votre domaine. Si PS_SHOP_DOMAIN_SSL pointe ailleurs, vos redirections, vos liens de paiement et vos e-mails transactionnels partent chez quelqu’un d’autre.
L’ordre des opérations compte
C’est l’erreur qui coûte le plus cher, et je l’ai vue faire souvent : nettoyer les fichiers d’abord.
Si vous supprimez les fichiers pendant que la persistance en base est toujours active, vous obtenez trois choses désagréables. Le malware revient, ce qui vous fait douter de votre propre nettoyage. Vous perdez la trace de l’infection initiale — les fichiers que vous venez d’effacer étaient vos preuves. Et vous brûlez du temps sur des cycles qui ne peuvent pas aboutir.
L’ordre qui fonctionne :
Sauvegardez avant de toucher à quoi que ce soit — fichiers et base, dans l’état compromis. Ça vous sert de preuve, et de filet si vous cassez quelque chose. Stockez cette sauvegarde hors du serveur, et ne la restaurez jamais telle quelle.
Coupez les accès. Mot de passe base, mots de passe employés, clés SSH et FTP, et surtout supprimez les comptes administrateurs illégitimes. Si l’attaquant garde un accès, tout le reste est du théâtre.
Neutralisez la persistance en base — les six points ci-dessus. Désactivez les tâches cron suspectes avant de les supprimer, ça évite les mauvaises surprises.
Nettoyez les fichiers ensuite. Comparez le cœur avec l’archive officielle de votre version exacte, téléchargée depuis prestashop.com. Les modules tiers, réinstallez-les depuis la source d’origine plutôt que de les rafistoler.
Attendez, et revérifiez. Une ré-infection se manifeste rarement dans l’heure. Reprenez toutes vos requêtes à 48 h, puis à une semaine. Tant que vous n’avez pas passé sept jours sans rechute, considérez que ce n’est pas fini.
Et si vous ne trouvez rien
Ça arrive, et ça ne veut pas dire que vous êtes propre. Les requêtes ci-dessus attrapent ce qui est visible en clair ; elles passent à côté d’un payload sérieusement obfusqué, d’un trigger SQL, ou d’une charge stockée dans une table de module tiers que personne n’inspecte.
À ce stade, deux options raisonnables. Faire appel à quelqu’un dont c’est le métier — un audit sérieux coûte moins cher qu’une boutique qui hémorragie ses clients pendant trois mois. Ou automatiser la recherche avec un outil qui dé-obfusque réellement au lieu de chercher des mots-clés.
C’est pour ce cas précis que nous avons développé Genisoft Security Scanner, disponible sur la marketplace officielle PrestaShop Addons (ou via sa page de présentation complète) : il inspecte les six emplacements ci-dessus plus quelques autres, décode les atob() et les tableaux fromCharCode pour révéler les domaines cachés derrière, et n’émet que des SELECT — il ne modifie rien. Autant être clair sur le contrat : c’est un outil de détection, pas un service de nettoyage. Il vous dit où regarder ; les décisions restent les vôtres.
Cela dit, ne payez rien avant d’avoir lancé les requêtes de cet article. Dans une majorité de cas, elles suffisent à identifier la cachette — et c’était précisément l’objectif.
Ce qui évite d’en arriver là
Les sauvegardes hors-ligne, testées : une sauvegarde jamais restaurée n’est pas une sauvegarde. Les mises à jour appliquées vite, surtout sur les modules tiers, qui sont le vecteur d’entrée le plus fréquent. L’authentification à deux facteurs sur le back-office. Un compte employé par personne, avec les droits minimums — pas un compte SuperAdmin partagé par toute l’équipe. Et un coup d’œil régulier sur ps_employee, ne serait-ce que deux minutes par mois.
Rien de spectaculaire. C’est simplement que la personne qui applique ces cinq points ne lira jamais cet article.
Pour aller plus loin :