TL;DR
- Les scanners matchent des signatures connues : un attaquant compétent se cache juste en-dessous de ce seuil.
- Shells PHP obfusqués, couches de
base64/eval/gzinflate, blobs hexa, variables-variables. - Backdoors planquées dans des fichiers légitimes, une ligne ajoutée à un vrai plugin, ou du PHP dans
uploads/. - Persistance : backdoors imbriquées, cron
wp_schedule_eventmalveillant, et portes dérobées en base de données. - L'arme la plus puissante : comparer chaque fichier aux checksums officiels de WordPress.org. Le reste en découle.
La fausse tranquillité du scan vert
Un scanner automatique, même payant, fonctionne sur un principe simple : il compare votre code à une base de signatures de malwares connus. Si un motif correspond, alerte. Sinon, écran vert, tout va bien. Le problème est dans ce « sinon ». L'absence de détection n'est pas une preuve de propreté, c'est juste l'absence de correspondance avec ce que l'outil sait déjà reconnaître.
Or un attaquant qui a réussi à entrer chez vous n'est pas un amateur qui dépose un shell téléchargé sur un forum. Il sait exactement comment ces bases de signatures fonctionnent, et il écrit son code pour passer juste en-dessous du radar. Résultat : le scanner affiche « 0 menace », le client est rassuré, et le site continue de spammer, rediriger ou miner pendant des mois.
Un scanner vous dit s'il y a quelque chose qu'il connaît. Un humain vous dit où est la chose qu'il n'a jamais vue, et pourquoi elle est là.
1. Les shells PHP obfusqués
Le symptôme : le site envoie du spam, sert des pages bizarres à Google, ou un fichier inconnu apparaît à la racine. Quand on l'ouvre, c'est une bouillie illisible, des centaines de caractères encodés, parfois sur une seule ligne interminable.
Pourquoi le scanner rate : l'obfuscation est faite pour casser le pattern-matching. On empile les couches, base64_decode qui contient du gzinflate qui contient du str_rot13 qui contient enfin l'eval, ou on cache la charge dans des blobs hexadécimaux et des variables-variables ($$x) reconstruites à l'exécution. La signature « visible » change à chaque infection ; il n'y a rien de stable à matcher.
La détection à la main : on ne cherche pas le malware, on cherche les marqueurs d'obfuscation. Un grep sur les fonctions qui n'ont presque aucune raison d'exister dans un thème sain : eval(, base64_decode(, gzinflate(, str_rot13(, create_function(, preg_replace avec le modificateur /e, ou des concaténations absurdes de variables. La densité est révélatrice : un fichier légitime n'aligne pas trois décodeurs imbriqués sur une ligne.
# marqueurs d'obfuscation, on cherche le COMPORTEMENT, pas la signature
grep -rEn "eval\(|base64_decode\(|gzinflate\(|str_rot13\(|create_function\(" wp-content/
grep -rEn "\\\$\\\$[a-z]" wp-content/ # variables-variables
grep -rEn "preg_replace\(.*/e" wp-content/ # exécution via modificateur /e (déprécié)
# une ligne anormalement longue = souvent une charge encodée
grep -rEl ".\{2000,\}" wp-content/ --include=*.php
2. Les backdoors planquées dans des fichiers légitimes
Le symptôme : aucun fichier « en trop ». Le scanner ne voit rien d'anormal parce qu'il n'y a rien de nouveau à voir, la porte dérobée vit dans un fichier qui a parfaitement le droit d'exister.
Pourquoi le scanner rate : deux techniques classiques. La première : une seule ligne ajoutée en bas d'un vrai fichier de plugin ou de thème, un functions.php, un index.php de plugin, au milieu de centaines de lignes légitimes. La seconde : un faux fichier au nom rassurant, du genre wp-cache-x.php ou class-wp-utils.php, déposé dans wp-content/uploads/, un répertoire où aucun PHP ne devrait jamais s'exécuter, puisqu'on n'y stocke que des médias.
La détection à la main : deux angles. D'abord, on traque tout .php dans les répertoires de contenu utilisateur, c'est l'anomalie la plus parlante. Ensuite, on regarde les dates de modification : un functions.php modifié trois mois après tous ses voisins, ou un fichier core dont le mtime ne colle pas avec la date de la version installée, raconte une histoire.
# du PHP exécutable dans uploads = drapeau rouge quasi systématique
find wp-content/uploads/ -name "*.php" -type f
# fichiers modifiés récemment alors que vous n'avez rien touché
find wp-content/ -name "*.php" -mtime -30 -type f -ls
# repérer un fichier dont la date jure avec ses voisins
ls -laT wp-content/themes// | sort -k6
3. Les backdoors imbriquées (et la réinfection en quelques heures)
Le symptôme : vous nettoyez, tout a l'air propre… et le lendemain matin le site est réinfecté. Vous recommencez, rebelote. C'est épuisant, et c'est le signe le plus clair d'un travail incomplet.
Pourquoi le scanner rate : les attaquants sérieux ne posent jamais une porte. Ils en sèment cinq, dix, dans des recoins différents, racine, plugin, mu-plugins, uploads, base de données. Vous en supprimez une (souvent la plus visible, celle que le scanner a peut-être pointée), et les autres restent. L'une d'elles re-télécharge la charge complète. La réinfection automatique en quelques heures n'est pas de la malchance : c'est la preuve qu'il reste de la persistance.
La détection à la main : c'est ici que la méthode artisanale écrase l'outil. On ne traite jamais une infection comme « un fichier à virer ». On cartographie tous les points d'entrée et de persistance avant de toucher quoi que ce soit, puis on nettoie d'un bloc. Un nettoyage one-shot qui « réussit » mais laisse une porte ouverte est juste un nettoyage raté en sursis.
4. Les tâches cron malveillantes
Le symptôme : vous avez supprimé chaque fichier vérolé, le site est nickel… et il se réinfecte quand même, à heure régulière. Aucun fichier suspect ne subsiste sur le disque entre deux réinfections.
Pourquoi le scanner rate : la persistance ne vit pas dans un fichier mais dans le planificateur de WordPress. Via wp_schedule_event, l'attaquant enregistre une tâche cron qui, à intervalle régulier, re-dépose la charge, télécharge un shell, recrée un admin, réinjecte le code. Le scanner inspecte les fichiers ; il ne lit pas la table wp_options où le cron WordPress est stocké (clé cron).
La détection à la main : on audite le planificateur. En WP-CLI, wp cron event list révèle les hooks programmés ; un hook au nom étrange ou inconnu des plugins installés est suspect. On croise avec une recherche du nom de ce hook dans le code : s'il n'est défini nulle part proprement, c'est une greffe.
# lister tout ce que WordPress a programmé
wp cron event list --fields=hook,next_run,schedule
# un hook inconnu ? on cherche qui l'enregistre
grep -rn "wp_schedule_event\|add_action( *''" wp-content/
# inspecter le cron brut stocké en base
wp option get cron --format=json
5. Les backdoors au niveau de la base de données
Le symptôme : le plus pervers. Vous avez reconstruit tous les fichiers à neuf depuis les sources officielles, et l'accès pirate persiste. Parce que la porte n'est plus dans un fichier : elle est dans la base.
Pourquoi le scanner rate : l'écrasante majorité des scanners n'analysent que le système de fichiers. Trois classiques côté base : (a) un compte administrateur injecté dans wp_users, souvent créé à une heure improbable et discret dans la liste ; (b) une option autochargée dans wp_options (autoload = yes) contenant du PHP sérialisé, ré-exécuté à chaque page ; (c) du code malveillant collé dans des widgets, des templates stockés en base ou des transients. Un nettoyage « fichiers seulement » laisse tout ça intact.
La détection à la main : on audite la base comme on audite le disque. On liste les administrateurs et on traque les comptes non reconnus ou aux dates d'inscription suspectes. On inspecte les options autochargées les plus lourdes, une valeur énorme et illisible en autoload est un drapeau rouge. Et on cherche les marqueurs d'obfuscation jusque dans le contenu de la base.
# tous les administrateurs, un compte que vous ne reconnaissez pas = alerte
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
# options autochargées les plus volumineuses (souvent une charge planquée)
wp option list --autoload=on --format=table --fields=option_name --orderby=size
# chercher des marqueurs d'obfuscation directement dans wp_options
wp db query "SELECT option_name FROM wp_options
WHERE option_value LIKE '%eval(%' OR option_value LIKE '%base64_decode(%'"
La technique qui surclasse toutes les autres
Si je ne devais garder qu'un seul outil, ce serait celui-là : comparer chaque fichier à sa version officielle. WordPress.org publie les checksums de chaque fichier du cœur ; les dépôts de plugins et de thèmes publics permettent la même chose. L'idée est imparable : peu importe à quel point une backdoor est obfusquée ou bien planquée, elle modifie ou ajoute un fichier, et donc elle dévie de la référence.
Là où un scanner cherche « est-ce que ce code ressemble à un malware connu ? », la comparaison aux sources pose une question infiniment plus forte : « est-ce que ce fichier est exactement celui que l'éditeur a publié ? » Toute réponse négative est un fichier à inspecter, qu'il soit dans une base de signatures ou pas. C'est la différence entre chercher des têtes connues et vérifier que chaque pièce est d'origine.
# vérifier l'intégrité du cœur WordPress contre les checksums officiels
wp core verify-checksums
# vérifier chaque plugin contre le dépôt officiel
wp plugin verify-checksums --all
# tout fichier signalé "ne correspond pas" ou "inattendu" passe à l'inspection manuelle
Ce que je retiens de vingt ans de nettoyage
- Un scanner dit SI, un humain dit OÙ et POURQUOI. L'outil est un point de départ utile, jamais une conclusion. L'écran vert ne prouve rien, il prouve seulement l'absence de signature connue.
- Le nettoyage n'est terminé que lorsque le point d'entrée est refermé ET toute la persistance retirée. Fichiers, cron, base de données, comptes. Si un seul vecteur reste, vous avez gagné quelques heures, pas la guerre.
- La réinfection automatique est le tell d'un travail incomplet. Quand un site « renettoyé » repart en vrille, ne cherchez pas un nouveau piratage : cherchez la porte que le précédent passage a laissée ouverte.
- Comparer aux sources officielles écrase tout le reste. Obfuscation, fichiers planqués, injections : tout se révèle face à la question « est-ce le fichier d'origine, oui ou non ? »
Si ce raisonnement vous parle, vous reconnaîtrez peut-être un cas voisin : le Japanese keyword hack, où Google indexe soudain des milliers de pages en japonais sur votre site. Même école : un scanner crie « infecté ! », mais seul un humain trouve où vit la persistance et pourquoi elle survit aux nettoyages express.
Votre WordPress est piraté, et le scanner dit que tout va bien ?
C'est exactement les cas que je traite à la main. Diagnostic gratuit, intervention en moins de 24h, et vous ne payez qu'une fois le site réellement propre. Plus de 200 sites sauvés, y compris ceux que d'autres avaient « déjà nettoyés ».
Diagnostic gratuit sur wp-pirate.fr ↗