Base WordPress injectée : articles, options et commentaires
Le cœur PHP est intact, la home a l'air normale, et pourtant Google indexe des pharmacies : la saleté est en base. Options `siteurl`, widgets, posts, commentaires. On compare un dump, on ne restaure pas « la base d'il y a un mois » sans la lire.
Le fichier PHP est intact, la base est sale. Options siteurl, widgets, posts. On compare un dump, on ne restaure pas « la base d'il y a un mois » sans lire.
Fichiers propres, base sale : le décalage
Un scan de fichiers (Wordfence, comparaison au zip officiel) peut tout passer au vert. Le thème n'a pas bougé. Le pirate, lui, a écrit dans `wp_posts`, `wp_options`, `wp_comments` ou une table d'extension. Le HTML servi aux visiteurs — et à Google — sort de là. C'est pour cela que « on a réinstallé WordPress » ne change rien : le cœur neuf relit la même base.
Les signes : `site:` qui montre des titres inconnus, un pied de page avec des liens que l'éditeur de thème ne contient pas, une `home` qui redirige alors que `.htaccess` est propre, des widgets texte que personne n'a posés. Testez en navigation privée et via l'inspection Search Console. L'écart entre votre écran d'admin et le HTML public est la preuve, pas un cache capricieux — encore que le cache puisse resservir du vieux malware.
Copiez la base avant toute requête de nettoyage. Un dump hors serveur, horodaté. Sans lui, chaque DELETE est irréversible, et vous perdez la fenêtre de dates pour un éventuel volet données.
Les options qui redirigent ou réécrivent le site
`siteurl` et `home` déplacés vers un domaine tiers, ou vers le même domaine avec un chemin bizarre : tout le site suit. `blogname` / `blogdescription` bourrés de mots-clés. `template` et `stylesheet` pointant vers un thème que vous n'avez pas choisi. Ce sont des lignes, pas des fichiers. phpMyAdmin ou WP-CLI (`wp option get siteurl`) les montre en trente secondes.
Les options de plugins de cache, de SEO, de redirection (Redirection, Yoast, Rank Math) portent parfois l'URL frauduleuse. Un scan de fichiers ne les voit pas. Grep le dump sur `http`, `<script`, `eval`, des domaines que vous n'opérez pas. Lisez avant d'UPDATE. Une option de licence ou de « stats » est un camouflage habituel.
Les `theme_mods_*` et `widget_*` injectent du HTML au rendu. Le fichier `footer.php` reste propre ; le visiteur reçoit le script. C'est le sujet voisin : JavaScript dans wp_options. Traitez les deux dans le même passage, pas en deux semaines.
- `siteurl` / `home` : encore vos URL, schéma compris (www, https).
- Plugins de redirection : aucune règle vers un tiers inconnu.
- Autoload `yes` récents : liste lue, pas vidée.
Articles, pages et révisions injectés
Des milliers de posts `publish` avec un slug asiatique ou un titre de casino : génération via plugin, via une option qui fabrique à la volée, ou vrais inserts en table. Tant que le générateur tourne, supprimer les lignes une à une est vain. Trouvez comment elles naissent (cron, mu-plugin, hook `init`), coupez, puis passez les URL en 404/410 et désindexez.
Plus discret : un article légitime dont `post_content` a gagné un paragraphe de liens ou un iframe. Les révisions (`wp_posts` type `revision`) gardent parfois l'état d'avant. Ne les videz pas « pour faire de la place » avant d'avoir comparé. Elles datent l'écriture.
`guid` et `post_author` aident. Un auteur inactif à 4 h du matin, ou un guid qui pointe vers un autre hôte, n'est pas un import maladroit — pas toujours, en tout cas. Croisez avec l'API REST si les access.log montrent des POST `/wp-json/`.
Commentaires et trackbacks comme charge utile
Des commentaires `approved` avec un lien ou un court script, des pingbacks en masse, un contenu dans `comment_content` qui n'apparaît que sur certaines pages : ce n'est pas « le spam Akismet classique ». Un thème qui affiche les commentaires sans échappement en fait une injection XSS stockée. Le fichier du thème peut être officiel ; la table, non.
Passez les suspects en `spam` ou `trash` après export, pas en DELETE immédiat si vous avez besoin de preuve. Fermez les commentaires sur les vieux articles si vous ne les modérez pas. Akismet pendant l'incident ne remplace pas la lecture des déjà-approuvés.
Les custom tables (forums, avis produits WooCommerce) jouent le même rôle. Un avis 5 étoiles avec un lien pharmacies, publié sans commande, est un contenu injecté. Filtrez par date et par `user_id` à zéro.
Comparer un dump, pas restaurer au jugé
La sauvegarde Updraft ou cPanel « d'avant le problème » contient souvent déjà l'injection — l'entrée datait. Restaurer « la base d'il y a un mois » remplace un état sale récent par un état sale plus vieux, et vous perdez les commandes, les contenus, les comptes clients d'un mois. On compare : dump actuel vs dump ancien vs, si vous l'avez, un export métier (produits, commandes) hors WordPress.
Outils : un diff sur les options critiques, un comptage de posts par date, une recherche de domaines étrangers. Un prestataire qui « restore last backup » sans ce passage vous revend un incident dans huit jours. Gardez le dump sale : il est la pièce, pas un déchet.
Si la base est trop grosse, priorisez `wp_options` autoload, les posts des 90 derniers jours, les users, les commentaires approved. Le reste se traite ensuite. L'urgence n'est pas d'« être propre partout », c'est de couper ce qui se sert encore aux visiteurs et à Google.
Ce qu'une réinstallation WordPress laisse derrière
Remplacer `/wp-admin` et `/wp-includes` ne touche pas la base. Recréer les tables en gardant `wp_posts` « pour ne pas perdre les articles » garde l'injection. Recréer les articles depuis un export XML WXR non relu les réimporte. Il n'y a pas de raccourci propre qui évite de lire.
Les tables d'extensions (SEO, cache, formulaires, WooCommerce) survivent à une réinstall du cœur. HPOS, logs, notes de commande : un skimmer n'y vit pas toujours, du spam ou un webhook détourné, parfois. Sur une boutique, isolez le checkout avant de jouer aux imports — guide PrestaShop / logique boutique et skimmer WooCommerce.
Sauvegarder avant chaque UPDATE
Chaque correction en base : `SELECT` de la ligne, export, puis `UPDATE`. Un `widget_text` vidé trop vite perd le seul exemplaire du payload dont vous aviez besoin pour Search Console ou l'assureur. Nommez les exports (`options-siteurl-avant.sql`).
En WP-CLI, `wp option update` est commode et dangereux : pas d'historique. Gardez le dump complet du début de journée comme filet. Si vous n'êtes pas à l'aise avec une valeur sérialisée PHP, ne l'éditez pas à la main : une sérialisation cassée met le site down. Déléguez plutôt que de « tenter le unserialize ».
Refermer l'entrée qui écrit encore en base
Une base relue et un admin encore ouvert, une clé REST `write`, un voisin qui a les identifiants MySQL : les lignes reviennent. Panel, FTP, CMS, compte MySQL, secrets SMTP, dans cet ordre. Inspectez les users et l'API. Un staging oublié qui partage la même base réinjecte ce que vous venez d'ôter.
Videz tous les caches après les UPDATE. Puis testez comme un visiteur et via Search Console. Si vous voulez un constat écrit plutôt qu'une série d'UPDATE au jugé, déclarez le site : le prix s'affiche avant l'accès.
- Options critiques relues et figées.
- Posts / commentaires injectés coupés à la source.
- Compte MySQL et admin CMS tournés.
- Caches vides, HTML public = HTML attendu.