Nettoyage malware WordPress : enlever les injections JavaScript douteuses

La première fois que j’ai vu une injection JavaScript “qui ne ressemble à rien”, c’était sur un site WordPress très banal, propre en apparence, avec un design inchangé depuis des mois. Pourtant, les plaintes ont commencé par vagues: des visiteurs qui basculaient vers une page de publicité au moment du clic, quelques téléchargements qui n’avaient rien à voir avec le contenu annoncé, et des alertes côté navigateur sur des scripts chargés depuis des domaines inconnus. Le site continuait à répondre, mais la confiance était cassée.

Ce type d’incident est fréquent parce que les injections JavaScript jouent sur la discrétion. Elles ne détruisent pas nécessairement le site. Elles le déforment. Elles détournent une seule page, un seul événement, un seul moment. Et comme WordPress est extensible, un fichier “un peu étrange” peut facilement passer pour un plugin mal configuré, un thème modifié, ou même une fonctionnalité marketing. Le but ici, c’est de décoder ce qui se passe, puis de faire un nettoyage malware WordPress efficace, en se concentrant sur les injections JavaScript douteuses.

Le scénario typique d’une injection JavaScript

Une injection “moderne” ne se contente plus de coller un bloc script au hasard. Elle utilise souvent trois mécanismes:

D’abord, une condition. Le script ne s’exécute que dans certains contextes, par exemple uniquement pour les utilisateurs non connectés, ou uniquement sur mobile, ou uniquement quand la page contient un texte précis. Ensuite, un chargement dynamique: le script injecté charge un fichier externe depuis un domaine qui change, ou qui ressemble à un service légitime. Enfin, une action furtive: redirection, ajout de balises invisibles, modification du DOM, appel à un tracker, ou déclenchement d’un téléchargement.

Dans un cas concret, le code incriminé était placé au milieu d’une balise footer, mais enveloppé dans une logique “safe” qui semblait fonctionner pour presque tous les navigateurs. Les tests manuels échouaient. Les redirections n’apparaissaient qu’après une durée d’attente, et uniquement pour certains User-Agent. Autrement dit, le script n’était pas une page de spam pleine et entière, c’était un guichet. La porte s’ouvrait quand la personne était “dans le bon profil”.

Quand on comprend ce pattern, on comprend aussi pourquoi il faut inspecter le rendu réel. Se contenter de chercher “script” dans les fichiers ne suffit pas.

Repérer l’injection sans se fier uniquement aux fichiers

La première étape que je privilégie consiste à observer ce que le navigateur exécute réellement. L’inspection réseau ne ment pas, à une nuance près: il faut se mettre dans les conditions les plus proches de celles des victimes.

Ouvrez la page suspecte, puis regardez:

    Les requêtes réseau JavaScript qui ne viennent pas de vos domaines habituels Les appels à des URL qui incluent des paramètres bizarres, des noms de fonctions opaques, ou des chemins “minifiés” sans cohérence avec vos assets Les scripts inline (dans le HTML) qui ne devraient pas être là, par exemple un bloc inline dans le source visible Les redirections qui surviennent sans action utilisateur claire

Souvent, l’empreinte est suffisamment nette: des domaines aléatoires, des scripts externes “one-shot”, ou des chaînes qui ressemblent à des déballages (des fonctions “eval”, des chaînes concaténées, ou du base64 manipulé). Si vous voyez un script inline qui n’a aucun lien avec votre thème, votre charte, ou vos plugins connus, c’est un drapeau rouge.

image

Je garde aussi une habitude simple: tester au moins deux profils. Un profil navigation “propre” (navigation privée, sans cookies), et un profil avec cookies (utilisateur connecté si c’est une page interne). Les injections conditionnelles se révèlent souvent dans un seul des deux cas.

Pourquoi WordPress laisse parfois passer ces injections

WordPress est stable, mais l’écosystème autour est large. Entre le thème enfant, les plugins, les mini-helpers, les optimisations d’assets, les scripts de consentement et les intégrations marketing, il y a beaucoup d’endroits où un développeur peut ajouter du JavaScript. Un attaquant exploite la même surface.

Les causes typiques que j’ai rencontrées ne sont pas spectaculaires:

    Un plugin abandonné, avec un mécanisme d’upload ou d’édition de contenu trop permissif Un compte admin compromis via réutilisation de mots de passe Un thème “légèrement customisé” qui accepte des modifications sans revue Une chaîne d’accès qui passe par la modification de fichiers de thème, ou par l’ajout de code dans des templates Une faiblesse côté permissions, par exemple un fichier écrivable par le serveur ou un propriétaire incorrect

Ce qui rend le travail délicat, c’est que le code malveillant n’imite pas toujours une “victime” classique. Il peut se faire passer pour une optimisation de performance, un snippet de tracking, ou une intégration de tag manager.

Un nettoyage malware WordPress réussit dépend donc d’un autre point: reconstruire une ligne de vérité sur ce qui était attendu.

Cartographier ce qui a changé

Avant de toucher au code, je recommande de cartographier. Sans ça, on risque de supprimer un morceau malveillant tout en oubliant sa source, ou de casser un comportement légitime.

La méthode la plus pragmatique consiste à comparer:

    Le code source HTML généré (avant et après, si vous avez une copie ou un cache) Les fichiers modifiés dans votre installation (dates de modification) Les plugins et thèmes présents, avec une attention spéciale aux ajouts récents Les utilisateurs, surtout ceux qui n’ont pas été créés par vous

Si vous avez un backup propre et récent, même imparfait, c’est un avantage énorme. Sinon, l’“état probable” se reconstruit à partir de l’historique: ce qui était déployé, ce qui a été mis à jour, ce qui a changé après une campagne, une maintenance ou un appel à un prestataire.

Dans un audit que j’ai mené, les dates de modification pointaient vers un dossier thème, mais le code visible dans le template ne donnait rien. Le vrai problème était dans une dépendance incluse, chargée ensuite par condition. Autrement dit, le “où” ne se lit pas toujours au premier coup d’œil.

Les endroits où chercher les injections JavaScript

Une injection JavaScript sur WordPress peut se trouver à plusieurs niveaux. Par expérience, voici les zones qui reviennent le plus, dans un ordre de priorité réaliste:

D’abord, les templates du thème, surtout footer.php, header.php, et parfois des fichiers moins évidents comme functions.php, ou des partials inclus. Ensuite, les fichiers de plugin, en particulier ceux qui gèrent l’output côté front. Troisième piste: les options stockées en base de données. WordPress peut intégrer des scripts via certaines options, shortcodes, ou paramètres utilisés par des thèmes.

Il y a aussi les “chemins” indirects: un fichier qui ne contient aucun JavaScript visible peut exécuter une logique qui reconstruit du code au moment du rendu. Par exemple, un script externe peut être inséré via une fonction PHP qui écrit des balises au runtime, ou par l’ajout d’un hook WordPress.

Un piège classique: chercher seulement des chaînes “script” dans les fichiers. Les injections modernes utilisent parfois des encodages ou des concaténations. Vous pouvez ne rien trouver en texte clair. D’où l’intérêt d’inspecter le HTML généré et les requêtes réseau, puis de remonter vers la source.

Symptômes concrets à valider

Quand je traite ce type d’incident, je ne travaille pas sur des impressions. Je valide des symptômes observables, parce que ça conditionne le niveau de confiance et le plan d’action.

Voici ce que vous pouvez vérifier rapidement, sans toucher encore au système:

    Des scripts inline inconnus dans la page, surtout dans les pages qui ne nécessitent pas de tracking supplémentaire Des chargements externes de JavaScript vers des domaines non liés à vos services habituels Des redirections ou actions déclenchées après un délai, parfois seulement sur certains navigateurs ou appareils Des erreurs de contenu: éléments injectés dans le DOM, popups, overlay, ou liens qui changent de destination Une incohérence entre le HTML attendu (thème, templates) et le HTML réel renvoyé

Si plusieurs de ces signaux sont présents, vous êtes probablement face à une injection active, et pas juste à un “fichier sale” inoffensif.

La stratégie de nettoyage: supprimer, puis sécuriser, puis vérifier

Un nettoyage efficace se joue en trois temps: retrait de la charge utile, restauration d’un état propre, et prévention de la répétition. Si vous sautez la sécurisation, vous ne faites que repousser le problème.

Le retrait ne veut pas dire “effacer à l’aveugle”. Il faut localiser la source. Si vous coupez un morceau qui semble malveillant mais que la logique d’injection reste, l’attaque peut revenir. Parfois, elle revient immédiatement, parfois au prochain chargement. WordPress peut aussi mettre en cache des pages, ce qui masque le résultat.

Voici un plan d’action réaliste, que j’applique dans la plupart des cas:

Mettre le site en mode investigation: désactiver temporairement la mise en cache côté serveur et CDN si vous en utilisez, et observer à nouveau le HTML et le réseau. Identifier la source exacte de l’injection: trouver le fichier ou le hook qui génère le bloc inline ou l’appel externe. Utiliser l’inspection du rendu, pas seulement la recherche de texte. Retirer le code malveillant et restaurer les fichiers sains: remplacer thèmes et plugins par leurs versions propres, ou restaurer depuis un backup vérifié. Réinitialiser l’accès: changer les mots de passe, désactiver les comptes suspects, vérifier les rôles, et forcer une rotation des sessions si nécessaire. Vérifier et durcir: exécuter une recherche post-nettoyage (fichiers, base, permissions), contrôler les pages à risque, puis surveiller les logs sur une période courte mais ciblée.

Cette séquence vous évite le piège le plus fréquent: “j’ai trouvé un script, donc tout est réglé”. Sur ce type d’attaque, la charge utile n’est qu’une partie du système.

Remplacer plutôt que réécrire: l’option la plus sûre

Quand un thème ou un plugin a été modifié de façon suspecte, je préfère le remplacement à la “réparation au millimètre”. Réécrire à la main est long, et vous pouvez rater un morceau caché dans une fonction.

Le bon réflexe consiste à:

    Télécharger la version propre du thème ou du plugin (celle que vous utilisez réellement) Comparer avec vos fichiers actuels Remplacer les fichiers entiers plutôt que quelques lignes, surtout si vous ne pouvez pas garantir l’intégrité

Si vous avez des modifications légitimes sur un thème (typiquement un thème enfant), le remplacement complet du thème parent ne pose généralement pas problème, mais il faut être attentif à l’endroit exact où l’injection se trouve. Sur un thème enfant, vous devrez conserver vos modifications légitimes uniquement après avoir confirmé qu’elles ne contiennent pas d’exécutions suspectes.

Dans un cas, l’injection était dans un fichier functions.php du thème enfant, mais masquée dans une condition “if (is_admin())” inversée. Le site semblait normal pour l’équipe, car le code ne s’exécutait qu’en front avec certains paramètres. Ce genre de bug “à l’envers” est fréquent chez les attaquants, parce qu’ils bricolent pour passer les tests.

Cibler les hooks et les points d’output

Beaucoup d’injections passent par des hooks WordPress. Si vous voyez une présence de code inconnue en sortie, la question devient: quel hook l’appelle?

Les hooks courants, côté front, sont liés à la génération de contenu, à l’insertion de scripts en tête ou en pied de page, ou à l’affichage de contenus filtrés. Si vous identifiez le hook, vous pouvez remonter vers la fonction incriminée, puis vers le fichier.

Le piège: certains thèmes ou plugins utilisent une structure qui rend la lecture difficile, avec des fonctions variables ou des chargements conditionnels. Dans ce cas, je procède par élimination structurée: je désactive temporairement les plugins non essentiels, je teste la page, puis je réactive en groupe jusqu’à retrouver la source. Cette méthode prend du temps, mais elle réduit les “suppositions” qui coûtent cher.

Vérifier la base de données sans tomber dans la panique

Les injections JavaScript stockées en base existent. Elles peuvent être liées à des options, du contenu, ou à des zones “globales” utilisées par certains plugins (par exemple des champs de code injection).

Une vérification de la base doit rester ciblée. Je n’aime pas “scanner tout” sans plan, parce que WordPress a beaucoup de données sérialisées, et vous pouvez trouver des occurrences sans savoir si elles sont réellement actives.

Le meilleur angle est de faire correspondre le code observé dans le HTML (ou l’URL du script externe) avec les occurrences dans la base ou les fichiers. Si le domaine externe apparaît dans vos logs réseau, cherchez ce domaine dans votre installation. La correspondance est souvent le fil rouge qui vous mène à l’endroit où ça a été stocké.

Même quand le code est effacé des fichiers, l’injection peut rester en base si elle a été sauvegardée dans un champ utilisé au rendu.

Plugins et thèmes: attention aux “faux bons signaux”

Certains plugins “de sécurité” peuvent aider à repérer des modifications, mais ils ne garantissent pas qu’une injection est supprimée. Ils peuvent aussi déclencher des alertes sur des modifications légitimes après mise à jour.

Quand j’ai vu un site signaler une injection, la correction rapide a été faite, mais l’attaque est revenue sous une autre forme. La source était un mécanisme de replanting: l’injection initiale avait une fonction qui reconfigurait d’autres zones au prochain chargement. Tant que l’accès n’était pas “assaini” (mots de passe, comptes, tokens, permissions), la réparation restait instable.

C’est pour ça que le contrôle des identifiants et des rôles est un volet du nettoyage, pas une étape “après”. Je traite toujours l’accès en même temps que le code.

Durcir après le nettoyage: ce qui change vraiment le résultat

Une fois le script supprimé, votre objectif est d’empêcher qu’un attaquant réinjecte. Le durcissement n’a pas besoin d’être complexe, il doit être cohérent et maintenu.

Les points qui font la différence, dans l’ordre que je privilégie:

    Restaurer des mots de passe solides, en rotation des comptes ayant un rôle élevé Supprimer ou verrouiller les comptes créés récemment sans justification Vérifier les paramètres et rôles, notamment les permissions de modification de thème ou d’édition de fichiers Mettre à jour le cœur WordPress, les thèmes et les plugins, surtout ceux avec des permissions ou des fonctionnalités front sensibles Vérifier les droits d’écriture sur les dossiers, et l’architecture d’hébergement (certaines configurations rendent l’attaque plus facile)

Je conseille aussi de réduire la surface inutile: moins de plugins, moins de thèmes modifiés, moins de scripts “mystères” ajoutés au fil des demandes marketing.

Mesure de la réussite: comment savoir que c’est fini

Après nettoyage, je ne me base pas sur un seul test. Je valide avec plusieurs scénarios, parce que beaucoup d’injections sont conditionnelles.

La méthode que j’utilise généralement ressemble à ceci: je teste la page suspecte dans une fenêtre privée, puis sur un compte connecté. Je vérifie aussi au moins une page différente (parfois l’attaque se limite à une seule URL, parfois elle s’étend à des gabarits). Ensuite je vide les caches, côté navigateur et côté serveur si vous en avez, et je re-teste.

Côté sécurité, j’ajoute une vérification d’intégrité. Sans promettre l’invincibilité, l’idée est de détecter si un fichier a été ré-modifié depuis la correction. Si vous avez un système de surveillance ou des alertes de modification, c’est le moment de le calibrer.

Quand tout est propre, vous ne voyez plus les scripts externes inconnus, les redirections disparaissent, et les sources HTML redeviennent cohérentes avec vos templates.

Cas limites: quand “ça ressemble” mais ce n’est pas de la même attaque

Il existe des situations qui imitent une injection, mais qui ne relèvent pas du même niveau d’urgence.

Par exemple, un tag manager légitime peut insérer des scripts inline et déclencher des chargements externes. Si c’est votre service, vous devez retrouver la trace dans vos configurations. La clé est la cohérence: domaine connu, version attendue, règles de chargement conformes à votre déploiement.

Autre cas: un thème qui ajoute un script via un champ de configuration, et ce champ contient par erreur une URL erronée ou un extrait copié-collé. Ce n’est pas forcément une attaque, mais un incident de configuration. Le traitement peut être plus simple, mais l’approche reste la même: identifier la source réelle et corriger, pas seulement “supprimer le script visible”.

Enfin, parfois ce n’est pas WordPress qui est compromis en premier. Il peut y avoir un compromis de l’hébergement, un accès par un autre vecteur, ou une modification au niveau du reverse proxy ou du CDN. Dans ces cas, vous pouvez nettoyer WordPress, mais la charge revient parce qu’elle est replantée ailleurs. Les symptômes réseau aident beaucoup à trier, mais si l’URL injectée ressemble à un domaine tiers qui passe par vos couches d’infra, il faut élargir la recherche.

Recommandation pratique si vous devez agir vite

Quand l’incident est actif, vous voulez limiter l’exposition. Vous pouvez:

image

    Restreindre l’accès à l’administration (IP whitelist, page de maintenance, ou verrouillage temporaire) Désactiver temporairement les plugins non essentiels Remplacer rapidement les thèmes et plugins à risque par des versions propres Mettre en place une surveillance des requêtes sortantes

Je reste prudent sur la mise hors ligne totale, car certains sites ont des contraintes métiers. Mais si vous êtes en présence de redirections vers des contenus externes non maîtrisés, la question fichier admin suspect WordPress se pose sérieusement. L’objectif est d’empêcher que des visiteurs reçoivent un comportement malveillant pendant que vous enquêtez.

Quelques indices qui orientent vers le bon coupable

Sans “deviner”, certains signaux orientent assez bien:

Si vous voyez un script externe qui change d’URL à chaque visite, c’est souvent une logique de chargement dynamique. Si le code inline contient des morceaux encodés, compressés, ou manipulés au runtime, c’est rarement un simple tracking. Si l’injection n’apparaît que pour certains pays ou certains navigateurs, l’attaquant a pris la peine de cibler.

Et si vous constatez que l’injection revient après suppression, c’est presque toujours un problème d’accès non totalement corrigé. Le code réapparaît parce que l’attaquant a une porte de retour: un compte persistant, un token, une tâche planifiée, un fichier caché, ou une modification sur un autre composant.

Dans ce genre de cas, une étape de “durcissement” bâclée prolonge l’incident. Le nettoyage malware WordPress doit aller jusqu’au bout, pas s’arrêter au moment où l’écran semble redevenir calme.

Checklist finale, orientée “injection JavaScript”

Je termine avec une grille mentale, simple, mais efficace, pour juger la stabilité après correction. Elle ne remplace pas un audit complet, mais elle évite les erreurs qui reviennent souvent.

Vous devez pouvoir dire, en observant le navigateur:

    Les scripts inconnus ne se chargent plus sur les pages concernées Les redirections, overlays et changements de liens ne se déclenchent plus Les balises inline suspectes ont disparu du HTML rendu Les domaines externes nouveaux ne réapparaissent pas après un délai Les fichiers et plugins modifiés ont été restaurés proprement et l’accès est sécurisé

Si ces cinq points sont vrais, vous êtes en bonne voie. Si l’un d’eux échoue, il faut reprendre la cartographie, notamment la source en sortie HTML et la persistance côté accès.

Si vous voulez, décrivez-moi le contexte (type de site, plugins récents, URL concernée, et ce que vous observez dans l’onglet réseau: nom de domaine du script, présence d’un bloc inline, fréquence des redirections). Avec ces éléments, je peux vous aider à structurer l’enquête pour localiser précisément l’injection JavaScript douteuse, sans casser un thème ou un plugin légitime au passage.