Quand un site WordPress “tombe malade”, la première réaction est souvent de penser à l’hébergement, à un thème cassé ou à une mise à jour oubliée. Pourtant, dans une grande partie des cas, le point de départ se trouve plus en amont: un fichier PHP modifié, un code injecté, un script de porte dérobée glissé là où personne ne regarde. Et si vous avez déjà dû faire face à ce genre de mésaventure, vous savez que la différence entre un nettoyage rapide et une rechute se joue sur la méthode.
Dans cet article, je vais me concentrer sur un endroit qui revient souvent dans les infections: le fichier functions.php. On y trouve du code utile, oui, mais aussi des charges malveillantes camouflées dans des fonctions censées servir à des optimisations, des redirections ou des “ajustements”. L’objectif est simple: vous aider à enlever virus WordPress de façon réaliste, en détectant les scripts dans functions.php, puis en fermant les portes qui ont permis l’injection.
Pourquoi functions.php est une cible fréquente
Le fichier functions.php existe dans les thèmes et parfois dans certains contextes de plugins (selon la structure). Il est chargé au moment où WordPress initialise le thème, ce qui en fait un endroit idéal pour exécuter du code à chaque page chargée.
Un attaquant cherche généralement deux choses.
D’abord, la persistance. Un site infecté ne se contente pas d’envoyer une charge une seule fois. Il veut que le code reste actif, même après des redémarrages ou des nettoyages partiels. Modifié functions.php permet de rejouer une logique malveillante à chaque visite.
Ensuite, l’exécution discrète. Beaucoup d’injections se présentent sous forme de blocs PHP “banals”: conditions, chaînes encodées, fonctions anonymes, base64, gzinflate, str_rot13, ou appels à eval et assert. Ce sont des marqueurs qui ne prouvent pas à eux seuls une attaque, mais qui doivent déclencher un examen.
J’ai vu des cas où le code était ajouté au tout début du fichier, juste après un commentaire, ou à la fin, en “complément”. Dans d’autres, il était placé au milieu d’une fonction légitime, avec quelques lignes qui, isolées, paraissaient presque inoffensives. Le danger, c’est que les infections se branchent sur des chemins rarement parcourus, par exemple uniquement quand un certain paramètre HTTP est présent, ou uniquement pour un type de navigateur précis.
Le premier réflexe: clarifier le type d’infection
Avant de supprimer du code à l’aveugle, j’insiste sur un point pratique: toutes les infections ne se ressemblent pas. Certaines injectent du contenu côté navigateur (redirections, scripts JavaScript), d’autres compromettent le serveur (fichiers uploadés, webshell), et d’autres agissent uniquement sur le contenu (injection dans la base de données).
Dans le cas qui nous intéresse, la présence de scripts dans functions.php est souvent liée à des comportements comme:
- redirections vers des domaines douteux selon l’IP ou l’agent utilisateur, chargement conditionnel de scripts distants, exécution d’un payload encodé (base64, compression, décodage dynamique), création de nouveaux comptes admin, ou modification de rôles, ouverture de failles via des endpoints discrets.
La façon dont le site se comporte vous donne souvent des indices avant même d’ouvrir les fichiers.
Par exemple, si vous remarquez que seules certaines pages sont touchées, ou si c’est “tout d’un coup” après un thème mis à jour, ça oriente. Si au contraire tout devient instable, avec des erreurs PHP, ça peut indiquer qu’un code a cassé la logique du site.
Repérer sans casser: faire un état des lieux propre
Avant de chercher “l’aiguille”, je recommande de figer l’aiguille. Autrement dit, sauvegarder et cadrer.
Commencez par créer une copie de votre installation WordPress, idéalement au niveau fichiers et base de données. Si vous êtes sur un serveur où vous pouvez cloner le répertoire, faites-le. Si vous êtes sur une plateforme managée, prenez une sauvegarde via l’interface et, si possible, une export de base.
Ensuite, identifiez votre architecture exacte:
- thème actif et thèmes enfants, plugins activés, présence de fonctions “modifiées” (différences par rapport au thème d’origine), éventuels MU-plugin (plugins mu) ou snippets hors du thème.
Pourquoi c’est important? Parce que le bon réflexe consiste rarement à “tout effacer et réinstaller”. On veut enlever virus WordPress, oui, mais aussi éviter de supprimer des personnalisations qui n’ont rien à voir avec l’infection.
Enfin, préparez une approche de comparaison. Si votre thème vient d’un fournisseur, vous pouvez souvent récupérer la version saine du thème et comparer son functions.php. Le delta saute aux yeux quand il y a une injection.
Où regarder dans functions.php (et ce qui doit vous faire lever un sourcil)
Un functions.php “normal” a des blocs lisibles: appels à des hooks (add_action, add_filter), fonctions nommées, conditions de type is_home(), is_page(), current_user_can(), wp_enqueue_style(), wp_enqueue_script(). Même quand le fichier est long, il est généralement cohérent avec le style du thème.
Une injection, elle, se signale souvent par un ou plusieurs éléments.
Premiers signaux, très concrets:
- chaînes codées et décodage dynamique, utilisation de eval ou assert, appels à des fonctions système comme shell_exec, system, exec, passthru, proc_open, requêtes réseau bizarres via file_get_contents, curl_exec ou fsockopen, création ou modification de fichiers sur le serveur, conditions basées sur des valeurs suspectes, comme des “secrets” dans les paramètres GET/POST.
L’autre catégorie de signaux, plus subtile: le code est “propre”, mais placé à un endroit incohérent. Une fonction censée être un hook peut contenir une logique qui charge du code externe ou qui manipule des superglobales de façon inhabituelle.
Si vous avez accès, comparez la taille du fichier. Dans plusieurs incidents, functions.php finit plus gros, avec des blocs ajoutés en fin de fichier. Un simple diff entre version saine et version actuelle suffit souvent à trouver.
Méthode de détection pas à pas: du symptôme au bloc suspect
Voici comment je procède, en pratique, quand je suspecte un script injecté dans functions.php.
D’abord, ouvrez le fichier concerné en mode lecture et cherchez les marqueurs. Sans vous noyer, gardez une liste mentale de fonctions et patterns à surveiller. Par exemple eval(, assert(, base64_decode(, gzinflate(, gzuncompress(, str_rot13(, et des fonctions réseau ou accès fichier comme file_get_contents(, fopen( ou curl_init(.
Ensuite, lisez le contexte. Une simple occurrence peut être légitime. Certains thèmes utilisent base64_decode pour des données d’image ou pour un petit encodage d’asset, et rarement utilisent eval. Le contexte vous dit si le code construit une charge exécutable ou s’il traite des données.
Troisième étape, vérifiez les conditions. Quand vous voyez une condition de type if avec un mélange de $_GET, $_POST, $_COOKIE ou de variables serveur, redoublez d’attention. Un site infecté aime les déclencheurs rares pour rester discret.
Quatrième étape, repérez les appels à des fonctions qui créent d’autres actions. Par exemple, si le code finit par appeler wp_insert_post, update_option, wp_mail, wp_update_user, ou register_user, ça mérite une vérification minutieuse.
Cinquième étape, tracez le flux. Si le bloc suspect charge un contenu, où va-t-il ensuite? S’il est décodé puis passé à eval, c’est presque toujours une injection. S’il est utilisé pour composer une URL de redirection, regardez les cibles.
Le but n’est pas d’“interpréter” tout le code à la main. Le but est de décider rapidement quoi enlever, tout en gardant la possibilité de restaurer une version saine.
Une checklist courte pour identifier les scripts ajoutés
Je vous propose une checklist simple et limitée, parce que c’est ce qui marche le mieux sur un serveur vivant, quand vous devez agir vite sans perdre le fil.
- Rechercher eval(, assert(, base64_decode(, gzinflate(, str_rot13(, puis vérifier le contexte d’usage Repérer toute logique qui lit $_GET, $_POST, $_COOKIE ou $_SERVER pour déclencher une action Vérifier les appels à des fonctions réseau (file_get_contents, curl_exec, fsockopen) ou d’écriture fichier (fopen, file_put_contents) Comparer la taille et le contenu du functions.php avec la version du thème d’origine (diff) Contrôler les conditions d’exécution, si elles ne sont pas liées à votre besoin réel
Si vous cochez plusieurs points, l’hypothèse devient très solide.

Exemples typiques de patterns d’injection dans functions.php
Sans vous noyer dans du code, voici des “formes” qu’on voit souvent.
Un cas courant: le fichier se met à contenir une variable contenant une longue chaîne, souvent base64. Ensuite, dans une condition, on fait un décodage puis on exécute le résultat via eval. Le tout est parfois enveloppé dans une fonction anonyme ou un bloc qui s’exécute seulement si un paramètre existe.
Autre forme: une chaîne qui n’est pas base64, mais compressée via gzinflate ou gzuncompress. Le thème semble “normal” au début, puis la fin du fichier se termine par une petite mécanique de décodage. Ce mécanisme sert rarement à autre chose que d’exécuter un payload.
Autre signal: du code qui charge un script externe via une URL. Parfois, l’URL est encodée, parfois elle est construite à partir d’une combinaison de morceaux, parfois elle dépend d’un header https://gardewp.fr/ ou d’une plage d’IP.
Et parfois, le code fait semblant de faire une tâche classique. Il “enqueues” un script, sauf que l’asset point vers un domaine étranger. Le navigateur charge quelque chose qui exécute du JavaScript malveillant, et la page affiche un comportement louche. À ce stade, vous pouvez penser que le problème vient du front, mais la racine peut être côté PHP, dans functions.php.
Réparer intelligemment: supprimer, restaurer, puis valider
Une fois le bloc suspect identifié, la tentation est de supprimer immédiatement la portion qui semble malveillante. C’est souvent nécessaire, mais il faut le faire proprement.
1) Restaurer la base de comparaison
Si vous avez la version originale du thème, commencez par la restaurer côté fichiers, puis ré-appliquez vos modifications une par une. Ça évite de garder des morceaux qui n’étaient pas au centre du problème.2) Si vous devez garder des personnalisations
Si le thème enfant contient aussi des ajouts, gardez uniquement ce qui est utile. L’infection se trouve parfois dans le thème parent, parfois dans l’enfant. Il faut donc vérifier les deux si vous utilisez un child theme.3) Valider la syntaxe
Après suppression, vérifiez qu’il n’y a pas d’accolades manquantes, de ; oubliés, ou d’erreur introduite par une suppression “au milieu”. Dans les infections, le code est souvent ajouté de façon brute, donc la structure environnante peut être fragile.
4) Mettre en place un contrôle
Refaites une recherche sur les marqueurs dans l’ensemble du thème et des plugins (au moins ceux actifs). Parfois, le fichier functions.php n’est qu’une première étape, un autre fichier contient la suite. Une rechute arrive vite si vous retirez seulement le bloc visible.Au-delà de functions.php: pourquoi le “nettoyage” ne suffit pas
Supprimer le code injecté, c’est enlever la partie visible, mais pas forcément le problème de fond. Les infections ont une cause, et tant que la cause reste, vous risquez de revoir le même scénario.
Les causes fréquentes:
- un compte admin compromis (mot de passe trop faible, réutilisé, ou volé), un plugin vulnérable ou obsolète, qui a permis l’escalade, un système de fichiers mal protégé, permissions trop permissives, des identifiants FTP/SFTP ou d’accès à l’hébergement exposés, une mise à jour non sécurisée ou une installation “à la main” sans contrôle.
Je recommande presque toujours de réinitialiser tous les mots de passe des comptes WordPress, surtout les rôles admin et éditeur. Ajoutez ensuite une authentification à deux facteurs si votre stack le permet. Et, point crucial, vérifiez la liste des utilisateurs. Une infection peut créer un compte discrètement.
Ensuite, regardez le dossier des uploads. Les webshell et scripts PHP peuvent se retrouver dans des endroits inattendus. Si vous avez la possibilité d’analyser aussi les fichiers récents, c’est très rentable.
Enfin, mettez à jour les composants. Mettre à jour après nettoyage est souvent plus sûr que le contraire, car certaines mises à jour peuvent écraser le code infecté, mais pas la persistance côté backdoor. Cela dit, si un plugin est connu vulnérable, il faut le traiter, sinon l’injection revient.
Vérifier que vous ciblez le bon fichier (et pas un faux positif)
Il existe aussi un piège classique: prendre un code “inhabituel” pour de la malveillance alors qu’il s’agit d’un vrai ajout.
Un thème peut contenir des logs, des règles conditionnelles, ou des intégrations tierces qui utilisent des décodages. Le problème, c’est quand le code exécute du contenu dynamique ou quand il fait du réseau sans justification claire.
Une règle simple que j’utilise: si le code fait une chose spécifique à un besoin connu (par exemple charger un script CSS/JS local, ou gérer des options du thème), il est plus probable que ce soit légitime. Si le code construit une charge à exécuter, ou s’il ouvre des communications pour charger quelque chose à distance, l’étiquette “infection” devient très probable.
Autre point de jugement: l’emplacement. Un hook wp_enqueue_scripts qui ajoute un script attendu, c’est normal. Une fonction anonyme qui apparaît sans lien évident, mélangée à des décodages agressifs, c’est un drapeau rouge.
Désamorcer la rechute: durcir l’accès et surveiller
Après un enlèvement de virus WordPress, la meilleure protection est de réduire la probabilité de nouvel accès non autorisé et d’augmenter la probabilité de détection rapide.
Concrètement, je conseille:
- limiter les droits d’écriture côté WordPress autant que possible, mettre en place un suivi des modifications de fichiers si votre hébergement le permet, vérifier régulièrement les plugins et thèmes, surtout après des mises à jour, appliquer des mots de passe forts, et unique, pour chaque compte, garder un œil sur les comptes utilisateurs nouveaux ou modifiés.
Un détail qui fait gagner du temps: conserver la date de l’incident. Si le site a commencé à agir “autour d’une certaine heure”, vous pouvez regarder les fichiers modifiés après cette heure. Ça réduit énormément l’espace de recherche.
Cas réaliste: quand l’infection est “invisible” au premier coup d’œil
Je vous donne un exemple typique, sans coller à un extrait exact de malware. Parfois, functions.php ne “crie” pas. Vous voyez un fichier long, avec des hooks classiques. Puis, au milieu, une section tient en quelques lignes, pas très longue. Elle ne contient pas eval. Elle ne contient pas forcément de réseau direct.
Mais elle manipule un paramètre pour ensuite déclencher un envoi de requête ou une redirection. Le déclencheur dépend d’un champ. La plupart des visiteurs ne le déclenchent jamais, donc vous n’observez pas tout. L’attaque peut exister, mais vous la voyez seulement en test.
C’est là que l’analyse de conditions devient déterminante. Si vous trouvez une condition qui parle à $_SERVER['HTTP_USER_AGENT'], $_GET['...'] ou $_COOKIE['...'], vous devez aller au bout. Même si ça ne contient pas de mots d’injection évidents.
Et dans certains cas, le script malveillant ne modifie pas le contenu, il modifie seulement un comportement. Par exemple, il “reconfigure” des hooks pour qu’un footer chargé en JS redirige l’utilisateur. Le symptôme n’apparaît https://gardewp.fr/nettoyage-malware-wordpress/ pas comme “du contenu injecté”, mais comme une navigation étrange.
Pièges de méthode: ce qu’il ne faut pas faire
Quand on veut enlever virus WordPress, le manque de calme peut coûter cher. Quelques erreurs reviennent.
Ne pas supprimer sans sauvegarde. Une suppression rapide peut casser un thème ou supprimer un bout indispensable. Quand vous réalisez la casse, vous perdez du temps à reconstruire.
Ne pas réinstaller sans vérifier. Repartir d’une base saine n’empêche pas la persistance si l’accès a été compromis ou si d’autres fichiers infectés restent. La “réinstallation propre” ne remplace pas l’enquête.
Ne pas se limiter à un seul fichier. functions.php est fréquent, mais ce n’est pas le seul point possible. Un plugin peut avoir été modifié. Un fichier uploadé peut servir de relais. Une base de données peut contenir du code ajouté à des options.
Transformer la détection en routine
Une infection n’arrive pas seulement un jour. Elle s’installe, elle attend, elle réagit. La meilleure stratégie, après nettoyage, consiste à installer une routine de vérification légère mais régulière.
Je ne parle pas d’un audit interminable. Juste de quelques habitudes:

- avant une mise à jour majeure, prenez une copie, après mise à jour, vérifiez les fichiers modifiés et les accès utilisateurs, maintenez un inventaire de plugins et retirez ceux qui ne servent plus, surveillez les anomalies de comportement (redirections, contenu étrange, popups, erreurs PHP).
Et surtout, si vous remarquez une modification inattendue de functions.php, ne la “tolérez” pas en vous disant que ça doit être un ajustement du thème. Même si le code ne paraît pas dangereux, un changement non prévu doit déclencher une comparaison.
Conclusion pratique: enlever virus WordPress part de l’examen, mais finit par la sécurisation
Détecter des scripts dans functions.php, c’est souvent la partie la plus visible et la plus gratifiante, parce que vous pouvez enfin nommer ce qui cloche. Une fois le bloc suspect identifié, retiré et remplacé par la version saine, le site revient généralement à un état stable.
Mais l’étape qui évite la rechute se passe ailleurs: sécuriser les accès, traiter la cause, mettre à jour les composants réellement vulnérables, surveiller les comptes et les fichiers. Sans ça, functions.php redeviendra juste le prochain endroit où un attaquant se donne rendez-vous.
Si vous voulez, décrivez votre cas (thème actif, présence d’un child theme, comportement observé, et si vous voyez des marqueurs comme base64_decode ou eval dans functions.php). Je peux vous aider à interpréter ce que vous avez sous les yeux et à prioriser les actions, sans vous faire perdre du temps.