Supprimer malware WordPress : supprimer les redirections via JS injecté

Quand un site WordPress commence à envoyer les visiteurs vers des pages “bizarres”, les premiers réflexes sont rarement techniques. On vérifie le thème, on regarde les plugins installés, on pense à une mise à jour ratée. Et puis, parfois, on tombe sur le vrai fil rouge: un JS injecté qui déclenche des redirections au chargement de la page.

Dans cet article, je vais détailler la façon dont j’aborde ce type d’infection, surtout quand l’objectif du malware est clair: rediriger vers une URL contrôlée par l’attaquant. L’approche combine observation (ce que le navigateur fait vraiment), analyse (où le JavaScript se cache), neutralisation (supprimer proprement) et durcissement (éviter que ça revienne).

Le scénario typique: redirections “au clic” ou “au chargement”

Il existe deux familles de comportements qui reviennent très souvent.

La première, c’est la redirection au chargement. La page s’affiche une fraction de seconde, puis bascule vers un autre domaine. Sur mobile, on le voit parfois seulement quand on arrive sur le haut du site ou juste après le rendu du contenu.

La seconde, c’est la redirection déclenchée par un clic, un scroll, ou même sans action visible mais après un délai court. Dans ce cas, un script écoute un événement, puis force window.location ou modifie un élément pour faire croire qu’il s’agit d’un lien normal.

Dans les deux cas, le malware se manifeste rarement “en pleine page”. Il s’appuie plutôt sur des mécanismes d’injection: une balise script ajoutée au mauvais endroit, un bout de code dans un fichier de thème ou de plugin, ou une modification discrète d’un fichier d’inclusion (header, footer, fonctions du thème, ou parfois un fichier dans wp-content).

Ce qui m’aide le plus, c’est de traiter le symptôme comme une preuve: le JavaScript est là, il s’exécute, donc la question devient “où est-il injecté” et “comment l’attaquant le maintient”.

Pourquoi le JavaScript est souvent la dernière étape d’une chaîne d’attaque

Le JS injecté est généralement la couche visible. Mais ce qu’on veut vraiment comprendre, c’est la persistance.

Souvent, l’attaquant a d’abord obtenu un point d’entrée: un compte administrateur compromis (mot de passe faible, réutilisation, session détournée), une vulnérabilité de plugin ou de thème non corrigée, ou une faiblesse côté hébergement (permissions trop larges, plugins de type “file manager” restés en place, etc.).

Ensuite, il installe la capacité à modifier le rendu du site. Parfois c’est une simple insertion dans un fichier chargé à chaque visite. D’autres fois, c’est une logique plus subtile: un fichier “jumeau” tronqué, une variable injectée via options WordPress, ou une modification d’un fichier de cache.

Quand c’est du JS de redirection, l’attaquant gagne deux choses:

    il détourne le trafic sans forcément casser tout le site, ce qui rend la détection plus difficile, il peut cibler des utilisateurs (selon l’agent navigateur, la langue, la région, ou juste au premier chargement après cookies effacés).

Le résultat, c’est qu’on ne peut pas se contenter de “supprimer le script” une fois. Il faut s’assurer que le script ne se régénère pas via une autre source.

La première étape pragmatique: isoler la source sans casser la prod

Avant de supprimer quoi que ce soit, je recommande une méthode qui limite les dégâts. L’idée est simple: observer et localiser, puis seulement après intervenir.

Si votre site est en production, évitez les actions qui peuvent entraîner une perte d’accès totale (par exemple supprimer des fichiers sans comprendre lesquels sont utilisés). À la place, https://gardewp.fr/nettoyage-malware-wordpress/ je procède souvent ainsi:

    Je teste depuis un navigateur “propre” (navigation privée) pour voir le comportement sans influence de cache ou de cookies. Je vérifie rapidement si la redirection arrive aussi sur un accès serveur direct (par exemple en appelant l’URL exacte via curl). Même sans tout comprendre, ça donne une indication sur un script exécuté côté client ou une redirection HTTP côté serveur. Je regarde si d’autres pages sont touchées: pages publiques, pages d’administration, pages de catégories, posts récents. Une infection limitée à certaines pages suggère un point d’injection plus ciblé.

Cette phase est utile parce que les malwares ne se présentent pas tous pareil. Par exemple, une redirection HTTP (code 301/302) ne dépend pas du JavaScript injecté, alors qu’une redirection “dans le navigateur” en dépend.

Et si vous avez déjà un doute que le script est injecté, ne le “faites pas tourner” en boucle sur de multiples navigateurs. Nettoyer le cache et l’historique peut masquer la piste, surtout quand le malware a des conditions basées sur des cookies.

Ce que je recherche dans le code source: les indices qui ne mentent pas

Quand on suspecte un JS injecté, le point d’entrée est presque toujours visible dans le HTML rendu. Il peut être discret, parfois encapsulé dans un commentaire ou caché dans une balise qui n’a pas vocation à être là.

Ce que je vérifie en priorité dans le “Code source” ou l’inspecteur:

    la présence d’un inattendu, souvent minifié, une chaîne de caractères contenant des URL suspectes, ou des domaines récemment vus dans les logs, des appels à document.location, window.location, location.href, ou des remplacements DOM (par exemple innerHTML ou createElement), des constructions obscures du type eval(...) ou atob(...), parce qu’elles servent souvent à décoder une charge utile, des scripts qui ajoutent dynamiquement une autre balise script, comme si le code “charge ailleurs” la vraie logique.

Un détail que j’ai vu trop souvent: le script n’est pas toujours injecté dans le header ou le footer. Il peut être placé dans une zone “safe” du thème, par exemple dans un fichier qui gère les widgets, ou dans le rendu des menus. Le JS finit quand même par s’exécuter car la page finale inclut ce fichier.

Distinguer trois cas, parce que les actions ne sont pas les mêmes

Avant de passer à “supprimer malware WordPress”, je classe mentalement l’incident dans une des trois catégories suivantes.

Cas 1: injection simple dans un fichier du thème ou du child-theme

Ici, le JS est directement présent dans un fichier (souvent header.php, footer.php, ou un fichier d’inclusion personnalisé). Le site charge alors ce fichier à chaque page.

Cas 2: injection dans un plugin ou une bibliothèque

La présence du script dans un fichier de plugin est fréquente, surtout si un plugin a été compromis ou s’il reste des “mulets” abandonnés. On voit parfois du code dans un répertoire spécifique, puis chargé par require depuis le bootstrap du plugin.

Cas 3: injection via base de données ou options WordPress

Le script peut exister en base: options, post meta, champ de settings, ou contenu d’un champ d’interface qui se retrouve injecté dans la sortie. Dans ce cas, supprimer le fichier ne suffit pas. Il faut nettoyer l’option ou le champ infecté.

Le cas 3 est le plus piégeux. Parce qu’on peut supprimer les fichiers modifiés et constater que la redirection revient. J’ai déjà passé une après-midi entière à chercher un fichier modifié, alors que le script venait d’une option stockée et réinjectée à chaque rendu.

Inspecter proprement: navigateur, logs, et aperçu de la page infectée

Je privilégie une combinaison simple, sans s’enfermer dans des outils exotiques.

Côté navigateur, ouvrez l’inspecteur et cherchez:

    le script exact qui déclenche la redirection, la ligne source indiquée par le navigateur (parfois on peut remonter vers un fichier ou une portion de code), la présence d’un “stack trace” si la console affiche des erreurs autour du script.

Côté serveur, regardez les logs si vous avez accès:

    les requêtes vers d’autres domaines, les variations d’URL selon l’heure, les différences entre IP internes et IP visiteurs.

Si vous utilisez un pare-feu applicatif, un WAF, ou un service CDN, vérifiez aussi ce qu’il voit. Mais attention, ces couches ne remplacent pas l’analyse du contenu rendu.

image

Un truc concret: quand le malware est actif, il y a souvent une corrélation temporelle entre la fréquence des redirections et une modification de fichiers ou d’options. Si vous avez une traçabilité d’hébergement (modification timestamps, logs de fichiers), c’est un avantage.

Une méthode de suppression fiable quand il s’agit de JS injecté

La suppression “propre” se fait en trois temps: stopper l’exécution, retirer la source, puis vérifier la persistance.

1) Mettre le site à l’abri pendant l’enquête

Si l’infection envoie les visiteurs ailleurs, je préfère réduire l’impact pendant l’analyse. Selon votre organisation, ça peut vouloir dire:

    passer le site en mode maintenance, restreindre l’accès admin (pour éviter que l’attaquant ne réutilise des endpoints), désactiver temporairement un plugin suspect, si vous savez lequel.

Le bon sens ici, c’est de ne pas couper au hasard. Une désactivation massive peut casser la page d’administration et vous empêcher de gérer correctement la suite.

2) Identifier le point d’injection exact

Sur un site WordPress, l’injection via JS se trouve souvent via l’un des chemins suivants:

    un fichier du thème modifié, un fichier d’un plugin, un paramètre stocké en base, un fichier ajouté dans wp-content chargé automatiquement.

Je ne “répare” qu’après avoir localisé la source exacte. Sinon, vous avez toutes les chances de supprimer un symptôme et de garder le moteur.

3) Retirer et surtout nettoyer la persistance

Supprimer le JS tel quel dans un fichier, c’est utile. Mais si l’attaquant a aussi:

    modifié des hooks WordPress, ajouté un fichier qui s’injecte au chargement, créé un nouvel utilisateur administrateur, mis à jour des options,

Alors la redirection reviendra.

Une fois que la source principale a été supprimée, il faut revérifier le HTML rendu. Le script ne doit plus apparaître, et surtout il ne doit plus se reconstruire.

Checklist d’intervention (courte, mais efficace)

Voici la démarche que j’utilise quand je dois agir vite, sans perdre la rigueur.

    Mettre le site en maintenance le temps de l’analyse si la redirection est immédiate et visible Reproduire la redirection en navigation privée, puis repérer le script injecté dans le HTML rendu Rechercher le contenu suspect dans les fichiers (thème, child-theme, plugins, chargements) et en base si besoin Vérifier utilisateurs WordPress, rôles, et modifications récentes (y compris fichiers ajoutés dans wp-content) Tester à nouveau la page après suppression pour confirmer que le script ne revient pas

Où se cache le JS injecté, concrètement

Même si chaque site est différent, certains emplacements reviennent.

Dans le thème, je regarde souvent:

    header.php et footer.php, les fichiers d’inclusion personnalisés chargés via get_template_part, les templates de pages qui sont très visités, les fichiers functions.php quand ils contiennent des ajouts “bizarres” de scripts dans wp_head ou wp_footer.

Dans les plugins, je surveille:

    les plugins récemment installés, ceux qui ont des fichiers “nouveaux” ou dont la taille a changé brutalement, les fichiers qui contiennent des chaînes d’URL et du code d’exécution dynamique.

En base, je cible:

    options liées à l’injection de scripts, champs de type “custom code” si le thème en propose, meta liés à des pages ou à des posts qui génèrent un bloc HTML contenant le script.

Une fois, j’ai trouvé une injection dans un champ qui semblait être destiné à l’intégration d’un code tiers (un “tracking code” ou un espace pour scripts). Le propriétaire du site avait utilisé cette fonctionnalité au départ pour un outil marketing, mais le malware avait réécrit le champ pour y mettre sa redirection. Tant que vous ne regardez pas la donnée en base, vous pouvez passer à côté du vrai point.

Exemple de suppression: quand le script force window.location

Sans recopier du code dangereux ici, le pattern le plus fréquent ressemble à ceci: un script minifié ou décodé, puis une instruction qui redirige.

Ce que je fais dans ce cas:

Je supprime la portion malveillante du fichier où elle est injectée. Je vérifie qu’il n’existe pas une logique équivalente ailleurs. Par exemple, si le script appelle un fichier externe ou si une autre fonction injecte un autre script. Je teste plusieurs pages. Une injection dans un template peut toucher plusieurs URLs, pas seulement la page où j’ai vu le problème.

Ensuite, je passe à la vérification de l’origine. Si le fichier a été modifié, je m’assure qu’il n’y a pas un plugin ou un hook qui le remplace à chaque requête, ce qui arrive parfois via des scripts “droppers” ou des options.

Vérifier la base de données, sans tomber dans le piège du “tout effacer”

Quand l’injection vient de la base, l’approche doit être prudente. Supprimer “au hasard” des options peut casser le site.

Je préfère procéder comme suit:

    Chercher dans les tables WordPress des signatures présentes dans le HTML rendu (par exemple une portion d’URL ou un fragment du code JavaScript). Identifier l’option ou le contenu exact, puis supprimer uniquement ce qui correspond au script injecté. Vérifier les pages qui utilisent cette donnée (un champ d’admin injecté dans un template).

Si vous n’êtes pas à l’aise avec la base, il vaut mieux s’appuyer sur un export de sauvegarde, puis faire une correction ciblée, ou confier à quelqu’un qui a l’habitude des nettoyages WordPress.

Mon point de vigilance: certaines infections modifient aussi des champs de thème stockés en base, notamment si le site utilise des éditeurs ou des composants custom. Le script apparaît alors dans la sortie, même si les fichiers du thème semblent “propres”.

Détails qui changent tout: cache, minification et CDN

J’ai vu des propriétaires de sites conclure à tort “c’est réglé” alors que la redirection revenait 24 heures plus tard. La raison était souvent le cache.

Si vous utilisez:

image

    un plugin de cache, un service CDN, une minification côté serveur ou côté edge,

Le JS peut rester servi depuis un cache même après suppression dans les fichiers source.

Donc, après nettoyage, je fais toujours:

    vidange du cache de l’application, purge du cache CDN, régénération si nécessaire.

Je teste ensuite depuis plusieurs emplacements si possible (au moins deux réseaux). Un cache local côté navigateur peut aussi masquer une correction, d’où l’intérêt de la navigation privée.

Sécuriser le point d’entrée, sinon le malware reviendra

Une fois la redirection arrêtée, le travail n’est pas fini. La plupart des infections reviennent parce que le point d’entrée n’a jamais été neutralisé.

Sur WordPress, je vérifie systématiquement:

    tous les comptes utilisateurs, surtout ceux qui ont des dates de création récentes, les rôles anormaux (un compte admin ajouté discrètement), la présence de comptes inconnus, les fichiers qui ont été ajoutés dans wp-content ou ailleurs.

Je vérifie aussi les plugins et thèmes:

    que ceux utilisés sont à jour, que les plugins inutiles sont désactivés ou supprimés, que les thèmes hérités (child-theme) n’ont pas reçu de modifications non prévues.

Enfin, côté hébergement, j’insiste sur les permissions. Si un attaquant peut écrire dans des emplacements où il ne devrait pas, vous augmentez le risque de récidive.

Cas particulier: redirections qui ne montrent aucune balise script évidente

Parfois, le HTML source ne contient pas de balise script “lisible”. Le malware peut:

    charger un script externe depuis un domaine, utiliser une forme de base64 ou de décodage, s’intégrer dans une fonctionnalité légitime (un tracker, un widget, un script de consentement).

Dans ce cas, le navigateur exécute quand même le code, donc l’info est dans l’exécution, pas seulement dans le HTML statique.

Ma méthode devient alors plus orientée observation:

    je repère l’action de redirection dans l’inspecteur, je remonte la provenance du script (fichier, inline, source map si présent, ou URL de chargement), je “casse” l’exécution en test local, puis je reviens à la source de l’injection.

Cela demande plus de temps, mais ça évite de supprimer des choses qui ne sont pas la cause.

Quand il faut restaurer depuis une sauvegarde

Je recommande la restauration quand:

    les fichiers sont trop modifiés, vous n’arrivez pas à identifier clairement le point d’injection, plusieurs pages sont touchées avec une logique différente, il y a des signes de compromission plus large (nouveaux fichiers, comptes inconnus, changements massifs).

Restaurer “à une date saine” réduit la surface d’incertitude. Mais même dans ce cas, il faut corriger la cause (mises à jour, suppression du vecteur d’entrée, sécurisation). Sinon, vous ne faites que remettre le compteur à zéro.

Dans mes interventions, la restauration est souvent le choix le plus rentable quand le site est déjà partiellement “désorganisé”, sans vouloir passer par une chasse au détail interminable.

Repères de jugement: comment savoir si le malware est vraiment parti

Une infection “propagée” ne se reconnaît pas uniquement au fait que les redirections s’arrêtent.

Je considère le nettoyage comme validé quand:

    le script injecté n’apparaît plus dans le HTML rendu, les redirections ne se déclenchent plus en navigation privée, les URL et comportements suspects ne se reproduisent pas sur plusieurs pages, aucun fichier nouvellement ajouté ne reste, aucun utilisateur inconnu ne subsiste, les logs ne montrent plus de pics d’activité anormaux liés aux redirections.

S’il manque un de ces points, il peut rester une persistance.

Réduire la surface d’attaque après le nettoyage

Après un incident, je conseille d’installer des garde-fous. Certains sont légers, d’autres plus complets.

Sans faire une liste interminable, je mentionne les actions qui changent vraiment la donne:

    maintenir plugins et thèmes à jour, supprimer ce qui n’est plus utilisé, limiter les comptes admin, exiger des mots de passe robustes, activer une authentification forte si elle est supportée, surveiller les modifications de fichiers et les événements admin.

Ces mesures ne “garantissent” pas l’absence de nouvelle compromission, mais elles réduisent fortement la probabilité et accélèrent la détection si un nouveau problème arrive.

Derniers conseils pratiques si vous êtes en train de dépanner

Si vous êtes au milieu du dépannage, voici les erreurs que je vois le plus:

1) Supprimer un script sans chercher sa source, puis constater que ça revient. 2) Se focaliser sur une seule page, alors que l’injection est liée à un template, donc elle affecte tout un ensemble d’URLs. 3) Oublier cache et CDN, ce qui fausse les tests. 4) Traiter seulement les fichiers, sans vérifier la base et les utilisateurs.

Si vous voulez un repère simple: remontez toujours de la redirection vers la source de génération du HTML. Le JS injecté vous donne un chemin, il suffit de le suivre méthodiquement.

Aller plus loin: documenter l’incident pour éviter la prochaine crise

Quand vous avez fini, prenez quelques minutes pour noter ce que vous avez trouvé: le fichier modifié, l’option ou le champ en base, la date approximative, et la méthode de persistance.

Ce détail rend le futur dépannage beaucoup plus rapide. Et si vous travaillez en équipe, ça évite de refaire les mêmes hypothèses deux fois.

Le plus frustrant avec ce type d’infection, ce n’est pas seulement le nettoyage, c’est le temps passé à “recomprendre” après coup. Une trace écrite évite ce coût.

Si vous me décrivez le comportement exact (redirection au chargement ou au clic), le contexte (thème parent ou child-theme, plugins récemment installés, symptômes sur plusieurs pages, présence ou non de redirections en curl), je peux vous aider à choisir la piste la plus probable pour localiser précisément le JS injecté et appliquer une suppression ciblée, sans casser le site.