Il y a trois semaines, un client de Nantes — une PME de 12 salariés dans la distribution de matériel horticole — m’a contacté après avoir reçu une alerte de son hébergeur : « trafic sortant anormal détecté sur votre compte. » Ce genre de message, je le vois passer plusieurs fois par an depuis que j’ai commencé à travailler sur des infrastructures WordPress en 2008. Dans ce cas précis, la cause était bête et méconnue : le dossier wp-includes de son installation WordPress était directement accessible et mal configuré. Je vous explique ce que j’ai trouvé, pourquoi c’est un problème, et surtout comment je l’ai corrigé — avec les chiffres et le protocole exact.
Le contexte client : une alerte qui cachait un problème structurel
Le site en question tournait sous WordPress depuis 2019, jamais migré, jamais audité côté serveur. L’hébergeur mutualisé avait détecté environ 40 000 requêtes sortantes en 48h vers des domaines tiers — un schéma classique de site compromis utilisé comme relais de spam ou de phishing. Le client pensait à un piratage du mot de passe admin. Après connexion en SSH et premier passage sur les logs Apache, j’ai vu autre chose : des centaines d’appels directs à des fichiers PHP situés dans /wp-includes/, avec des paramètres suspects dans l’URL, et des réponses HTTP 200 — donc exécutées avec succès.
Ce n’était pas un piratage du compte admin. C’était une exploitation directe de fichiers PHP exposés dans un dossier qui n’a, par nature, aucune vocation à être appelé directement par un navigateur ou un script tiers.
Qu’est-ce que wp-includes, concrètement ?
wp-includes est l’un des trois dossiers racine de toute installation WordPress, avec wp-admin et wp-content. Contrairement à wp-content (qui contient vos thèmes, extensions et médias — donc du contenu que vous gérez), wp-includes contient le cœur logique de WordPress : les fonctions PHP internes, les classes qui gèrent la base de données, le moteur de templates, les bibliothèques tierces embarquées (comme PHPMailer pour l’envoi d’e-mails, ou SimplePie pour les flux RSS), et une bonne partie du moteur qui fait tourner l’API REST.
Le terme technique à retenir ici, pour les non-développeurs : ce sont des fichiers noyau (« core »), c’est-à-dire des fichiers d’infrastructure interne, jamais destinés à être appelés directement depuis l’extérieur. Un visiteur normal — ou un moteur de recherche — n’a strictement aucune raison de charger une URL du type votresite.fr/wp-includes/class-wp-query.php. Ce sont des briques que WordPress utilise en interne, via des appels PHP internes (include, require), jamais via une requête HTTP directe d’un utilisateur.
Sur le site du client, ce dossier contenait plus de 4 000 fichiers PHP répartis sur environ 200 Mo — c’est l’ordre de grandeur normal pour une version WordPress récente. Le problème n’était pas leur présence : ils doivent être là pour que le site fonctionne. Le problème, c’est qu’ils étaient directement exécutables depuis l’extérieur, sans aucune restriction serveur.
Pourquoi c’est un vrai risque pour une PME — pas juste de la théorie
J’entends souvent, chez mes clients dirigeants de PME, la même objection : « on n’est pas une cible, on est trop petits. » C’est faux, et je le vérifie sur le terrain depuis 15 ans. Les attaques automatisées ne ciblent pas des entreprises, elles ciblent des versions de logiciels vulnérables, scannées par milliers en quelques heures par des bots. Un dossier wp-includes mal protégé constitue trois catégories de risque concrètes :
- Exécution de code arbitraire — si une faille est découverte dans une bibliothèque embarquée (PHPMailer a eu plusieurs CVE critiques entre 2016 et 2021, dont une permettant l’exécution de code à distance), un accès direct au fichier concerné permet à un attaquant de l’exploiter sans passer par aucun mécanisme de sécurité de WordPress.
- Fingerprinting de version — certains fichiers de
wp-includes(commewp-includes/version.phpou les chemins de certains scripts JS/CSS versionnés) permettent à un attaquant d’identifier précisément la version de WordPress installée, donc de cibler les failles connues de cette version exacte. - Relais d’attaque — c’est exactement ce qui s’est produit chez mon client : certains scripts du dossier, combinés à une configuration serveur permissive, avaient été détournés pour envoyer des requêtes sortantes massives, transformant son hébergement en relais pour une autre attaque.
Sur les sites que j’audite depuis 2020, j’estime qu’environ 1 site PME sur 4 n’a aucune règle de blocage sur wp-includes au niveau serveur — ce n’est pas activé par défaut par la majorité des hébergeurs mutualisés, et ce n’est pas non plus une configuration que WordPress impose lui-même à l’installation. C’est un point mort classique entre « ce que fait WordPress » et « ce que fait l’hébergeur », et personne ne le couvre par défaut.
Le diagnostic : ce que j’ai vérifié en premier
Avant de toucher à quoi que ce soit, j’ai posé un diagnostic complet — c’est une règle que je ne transige jamais sur le terrain : on ne corrige pas avant de comprendre l’étendue exacte du problème, sinon on risque de casser le site en croyant le sécuriser.
- Vérification d’accessibilité directe — j’ai testé l’accès à plusieurs fichiers PHP connus du dossier via curl, en simulant une requête externe simple, pour voir si le serveur retournait une exécution PHP normale (code 200 avec sortie) plutôt qu’un refus (403).
- Lecture des logs Apache des 30 derniers jours — pour identifier tous les chemins
/wp-includes/ayant reçu des requêtes externes, et croiser avec les adresses IP sources (beaucoup provenaient de plages IP associées à des botnets connus, identifiables via AbuseIPDB). - Vérification de l’intégrité des fichiers core — comparaison des sommes de contrôle (checksums) des fichiers WordPress installés avec les sommes officielles, via
wp core verify-checksumsen ligne de commande WP-CLI. C’est l’étape qui m’a permis de confirmer qu’aucun fichier n’avait été modifié ou remplacé — le problème était une exposition, pas encore une compromission de fichier. - Version de WordPress installée — le site tournait en 6.1, alors que la version stable au moment de l’audit était la 6.4. Trois versions de retard, soit plus de 14 mois sans mise à jour core.
Ce diagnostic m’a pris 45 minutes. C’est le temps que je recommande de bloquer systématiquement avant toute intervention de durcissement — aller plus vite, c’est prendre le risque de masquer une compromission déjà en cours au lieu de la traiter.
La correction, étape par étape
1. Bloquer l’exécution PHP directe dans wp-includes
C’est la mesure la plus importante et celle qui a corrigé l’essentiel du problème. Sur un serveur Apache (le cas de ce client, mutualisé OVH), j’ai ajouté un bloc de règles dans le .htaccess à la racine du site, juste avant le bloc standard WordPress :
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php$ - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
Ce bloc renvoie une erreur 403 (accès interdit) pour toute tentative de chargement direct d’un fichier PHP dans wp-includes, tout en laissant WordPress continuer à les utiliser en interne — parce que ces règles ne s’appliquent qu’aux requêtes HTTP entrantes, pas aux appels PHP internes du moteur. C’est un point de confusion fréquent chez les administrateurs juniors : on a peur de « casser » le site en bloquant ces fichiers, alors que WordPress ne les charge jamais via HTTP en fonctionnement normal.
Sur un serveur Nginx (ce que j’utilise sur la majorité de mon propre parc de sites), l’équivalent dans le bloc server est :
location ~* ^/wp-includes/.*\.php$ {
deny all;
}
2. Corriger les permissions de fichiers
Deuxième point trouvé lors du diagnostic : les fichiers du dossier étaient en permissions 755 pour certains sous-dossiers, avec quelques fichiers en 777 — probablement hérités d’une installation manuelle ou d’un plugin mal désinstallé qui avait modifié les droits pour forcer une écriture. Un 777 signifie que n’importe quel processus sur le serveur peut lire, écrire et exécuter ce fichier, y compris un script malveillant déposé ailleurs sur un hébergement mutualisé partagé.
La norme que j’applique systématiquement sur les fichiers WordPress :
- Dossiers :
755(le propriétaire peut tout faire, les autres peuvent lire et exécuter, pas écrire) - Fichiers :
644(le propriétaire peut lire/écrire, les autres peuvent seulement lire — aucune exécution possible en dehors du moteur PHP-FPM lui-même) wp-config.php:600ou640selon la configuration du serveur — c’est le fichier le plus sensible du site, il contient les identifiants de la base de données en clair
Correction en ligne de commande, exécutée depuis la racine du site :
find wp-includes/ -type d -exec chmod 755 {} \;
find wp-includes/ -type f -exec chmod 644 {} \;
3. Mettre à jour le core WordPress
Avec 3 versions de retard, le site cumulait plusieurs correctifs de sécurité non appliqués, dont au moins une faille documentée affectant une bibliothèque de wp-includes entre les versions 6.1 et 6.3. J’ai fait la mise à jour en passant d’abord par une sauvegarde complète (fichiers + base de données), puis via WP-CLI :
wp core update
wp core update-db
Sur ce client, j’ai ensuite activé les mises à jour mineures automatiques (correctifs de sécurité) directement dans wp-config.php avec define('WP_AUTO_UPDATE_CORE', 'minor'); — un compromis raisonnable pour une PME sans équipe technique dédiée : les correctifs de sécurité s’appliquent seuls, les montées de version majeures restent sous contrôle manuel pour éviter une incompatibilité avec un plugin métier.
4. Masquer la version WordPress affichée publiquement
Dernier point, plus mineur mais qui réduit la surface de reconnaissance pour un attaquant automatisé : WordPress expose par défaut sa version dans le code source (balise meta generator) et dans les paramètres de version des fichiers CSS/JS chargés depuis wp-includes. J’ai ajouté ce filtre dans le thème enfant du client :
remove_action('wp_head', 'wp_generator');
add_filter('style_loader_src', 'strip_version_query', 15);
add_filter('script_loader_src', 'strip_version_query', 15);
Ce n’est pas une mesure de sécurité à elle seule — un attaquant déterminé peut identifier la version autrement — mais elle élimine les scans automatisés les plus basiques, qui représentent la majorité du bruit malveillant sur un site PME.
Le piège de sécurité que j’ai évité de justesse
C’est le point que je veux souligner le plus, parce que je vois cette erreur régulièrement chez des administrateurs pressés : ne jamais bloquer wp-includes en aveugle avec une règle trop large. Sur une première tentative de règle générique du type RewriteRule ^wp-includes/ - [F,L] (qui bloque tout le dossier, pas seulement les fichiers PHP), j’aurais cassé le chargement de certaines ressources JavaScript et CSS légitimes que WordPress sert directement depuis ce dossier — notamment les scripts de l’éditeur Gutenberg et certaines polices embarquées. Résultat : un site à l’apparence cassée, l’éditeur d’articles inutilisable côté client.
La règle correcte cible uniquement l’exécution de fichiers .php, jamais les fichiers statiques (.js, .css, polices) que WordPress a légitimement besoin de servir depuis ce même dossier. C’est la nuance qui sépare un durcissement efficace d’un site cassé — et c’est exactement le genre d’erreur qu’on ne voit qu’après l’avoir vécue une fois sur un client réel, pas en lisant un tutoriel générique.
L’action à faire dans les 24h si vous lisez cet article
Si vous gérez un site WordPress pour votre PME et que vous n’avez jamais vérifié ce point précis, voici le test immédiat, sans compétence technique poussée requise : ouvrez un navigateur et tapez votresite.fr/wp-includes/wlwmanifest.xml. Si le fichier s’affiche (plutôt qu’une erreur 403 ou 404), c’est le signe que votre dossier wp-includes n’a aucune règle de blocage sur les accès directs, et qu’il est temps de faire auditer votre configuration serveur avant qu’un scan automatisé ne le fasse à votre place.
Deuxième vérification rapide, tout aussi immédiate : dans votre tableau de bord WordPress, allez dans Tableau de bord → Mises à jour, et regardez si un bandeau vous indique une nouvelle version disponible. Si oui, ne la reportez pas — sur les 200+ sites WordPress que j’ai audités depuis 2019, le retard de mise à jour core reste la première cause de compromission que je constate sur le terrain, bien avant les mots de passe faibles.
Résultats mesurés chez le client
Intervention réalisée un jeudi matin, correction complète appliquée en 2h30 (diagnostic compris), avec redémarrage à froid du cache et vérification fonctionnelle complète du site (formulaire de contact, panier, back-office) avant de considérer l’intervention terminée. Résultats sur les 15 jours suivants, comparés aux 15 jours précédant l’intervention :
- Requêtes sortantes anormales : de ~40 000 sur 48h à zéro détecté par l’hébergeur
- Requêtes entrantes ciblant directement
/wp-includes/*.php: passées de 340/jour en moyenne à 12/jour (les 12 restantes recevant systématiquement une réponse 403, donc neutralisées) - Temps de chargement moyen des pages : amélioration marginale de 180ms, effet secondaire de la réduction de charge serveur liée à l’arrêt des requêtes malveillantes
- Alerte hébergeur : close sans autre incident depuis, avec surveillance maintenue sur 30 jours
Le coût de cette intervention pour le client : une demi-journée de mon temps. Le coût s’il n’avait rien fait et que l’hébergeur avait suspendu le compte pour abus (ce qui arrive, je l’ai vu deux fois en 2023 chez d’autres clients) : plusieurs jours d’indisponibilité totale du site e-commerce, sur un secteur où chaque jour de coupure représente une perte de chiffre d’affaires directe et mesurable.
Ce qu’il faut retenir
wp-includes n’est pas un dossier à supprimer ni à redouter — c’est le moteur même de WordPress, il doit rester intact et à jour. Le risque n’est pas dans son existence, il est dans l’absence de règles serveur empêchant son exécution directe depuis l’extérieur. En 15 ans de terrain, la combinaison qui fonctionne systématiquement reste la même : blocage ciblé de l’exécution PHP directe, permissions de fichiers correctes, core à jour, et surveillance des logs sur une base régulière — pas seulement quand l’hébergeur envoie une alerte. Une PME n’a pas besoin d’une équipe sécurité dédiée pour appliquer ces quatre points ; elle a besoin de quelqu’un qui les vérifie une fois, correctement, et qui remet en place les mises à jour automatiques pour que ça ne se reproduise pas.
Pour approfondir, consultez la page de manuel de chmod ainsi que celle de chown.