Scanner malware WordPress : repérer les injections dans wp_options et wp_posts

Sur un site WordPress, les infections ne se résument presque jamais à “un fichier malveillant”. Le plus délicat, c’est quand le malware s’installe sans bruit, au bon endroit, avec le bon langage, puis commence à fabriquer du contenu à la volée. Dans ce scénario, deux tables reviennent tout le temps dans les enquêtes: wp_options et wp_posts.

Ce que je décris ici n’est pas une théorie. C’est la mécanique que j’ai vue maintes fois sur des sites compromis via des failles de plugins, des identifiants réutilisés ou des scripts d’injection ciblant les autorisations WordPress. Le but n’est pas de “scanner”, au sens clic-magic, mais de repérer des traces concrètes dans la base, pour comprendre ce que le site fabrique et comment il le fait.

Pourquoi wp_options et wp_posts sont des zones “à risque”

WordPress stocke beaucoup de choses en base. Les options, c’est la configuration globale, les caches, certains réglages de plugins, mais aussi des morceaux de code et des paramètres “tenus hors de vue”. Quand un malware cherche à persister, il adore les endroits où il peut se relancer à chaque chargement sans toucher aux fichiers de thème ou de plugins. wp_options est une porte d’entrée parfaite.

wp_posts, c’est le contenu éditorial. En théorie, ce sont des articles, pages, brouillons, révisions, et des contenus liés aux médias. En pratique, un script malveillant peut:

    injecter des brouillons ou des pages “vides” qui deviennent des pages d’atterrissage selon l’agent utilisateur insérer du spam dans du contenu existant modifier des champs qui pilotent le front-end (shortcodes, meta, contenu généré)

Ce n’est pas seulement “du spam”. Quand l’injection est bien faite, elle déclenche un comportement à la demande: l’utilisateur voit quelque chose, le robot de recherche voit autre chose, et le malware garde une porte arrière pour évoluer.

Les symptômes côté site qui orientent vers la base

Avant d’ouvrir la base, je préfère collecter deux ou trois indices. Cela évite de s’acharner sur un faux positif.

Quelques signaux typiques, très “parlants”:

    le site charge lentement ou redirige certains utilisateurs, mais pas tout le monde des pages semblent “réécrites” alors qu’aucun auteur n’a publié de nouveaux plugins ou thèmes apparaissent, puis disparaissent, ou ne sont pas visibles dans l’interface des URL externes apparaissent dans le HTML sans cohérence avec l’éditeur (liens vers des sites de mauvaise réputation, paramètres étranges) des pages “normales” contiennent du contenu qui n’existe pas dans WordPress admin

Quand ces symptômes collent, je me tourne vers wp_options et wp_posts.

image

Rechercher les injections dans wp_options (sans tomber dans le piège du “trop large”)

La tentation, c’est de lancer un gros “SELECT *” sur toute la table, puis de filtrer ensuite. Mauvaise idée: vous allez produire du bruit, ralentir la base et passer à côté d’un détail utile.

L’approche pragmatique consiste à cibler le couple “option name” et “optionvalue”, et à chercher des marqueurs. Les infections n’utilisent pas toutes les mêmes marqueurs, mais il y a des constantes.

Les marqueurs à traquer dans option_value

Beaucoup d’injections contiennent au moins un des éléments suivants:

    chaînes de fonctions PHP (par exemple eval, base64_decode, gzinflate, str_rot13) fragments de balises HTML inattendues shortcodes ou expressions qui ne devraient pas être stockées en option URLs externes dans des options censées rester internes

Le piège: certains plugins légitimes stockent aussi des chaînes similaires. Exemple classique: un plugin de cache ou de minification peut contenir des bouts de code ou des patterns compressés. C’est pour ça que je travaille avec un filtrage raisonnable et un contexte.

Requêtes utiles, ciblées et “raisonnables”

Voici des requêtes SQL que j’utilise souvent. Elles ne remplacent pas une analyse globale, mais elles donnent rapidement des pistes.

-- 1) Options qui semblent contenir du PHP SELECT option_name, option_value FROM wp_options WHERE option_value REGEXP 'eval\\(|base64_decode\\(|gzinflate\\(|str_rot13\\(' LIMIT 50; -- 2) Options qui contiennent des fragments HTML ou du JS inline SELECT option_name, option_value FROM wp_options WHERE option_value REGEXP '