WordPress · 11 min · publié le 5 mars 2025

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.

Réponse directe

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.

base wordpress spam wp_options malware injection sql wordpress

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.

Une injection SQL n'est pas obligatoire. Un admin fantôme, l'API REST ou un plugin vulnérable écrivent « légalement » en base. Le résultat se ressemble ; l'entrée, non.

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.

Réimporter un dump non lu sur un site déjà « nettoyé » côté fichiers est le geste qui remet le skimmer ou le spam SEO en ligne.

Widgets, menus et pied de page en table

Les widgets texte, HTML personnalisé, et parfois les blocs de widgets Gutenberg, vivent en options. Un menu (`wp_terms` / `wp_term_relationships` + `theme_mods`) peut contenir des liens externes ajoutés. Le customizer affiche le thème « comme d'habitude » ; le rendu public inclut l'ajout.

Parcourez Apparence → Widgets et Menus après avoir vidé les caches. Si l'admin est trop lente ou truquée, lisez `widget_text`, `widget_custom_html`, `sidebars_widgets` dans le dump. Un iframe ou un `document.write` n'a rien à faire dans un widget « horaires d'ouverture ».

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.

Questions fréquentes

Un scan de fichiers vert veut-il dire que la base est propre ?

+
Non. Les scans signent des fichiers. Une option, un post ou un commentaire malveillant passe. Il faut grep le dump ou inspecter les options et les contenus récents.

Puis-je restaurer uniquement wp_options depuis une vieille sauvegarde ?

+
Dangereux. Vous ramenez d'anciens réglages (parfois déjà sales) et vous cassez des plugins plus récents. Restaurez des clés précises après lecture, pas la table entière « pour gagner du temps ».

Comment trouver du JavaScript en base sans phpMyAdmin ?

+
Dump SQL, puis recherche de `<script`, `eval(`, `fromCharCode`, `document.write`, ou d'un domaine inconnu. WP-CLI : `wp db search` si vous assumez la charge. Exportez les hits, ne les éditez pas en masse.

Les commentaires en attente de modération sont-ils dangereux ?

+
Moins que les `approved` affichés au public. Traitez les approuvés d'abord. Les pending peuvent attendre, sauf s'un thème les affiche par erreur.

Faut-il prévenir les clients si seuls des articles spam ont été ajoutés ?

+
En général non, ce n'est pas une lecture de table clients. Si l'entrée impliquait un export ou un admin sur WooCommerce, le constat tranche. Ne publiez pas « aucune fuite » tant que l'analyse n'est pas close.
À lire ensuite
JavaScript dans wp_options wp-cron et malware API REST abusée WordPress piraté Pages spam dans Google Déclarer mon site