Sécurité WordPress : lire les attaques dans les logs
Outils web

Sécurité WordPress : lire les attaques dans les logs

Repérer force brute, abus de xmlrpc.php, scans d'extensions et injections dans les logs WordPress, puis répondre avec fail2ban, limitation et WAF.

Mis à jour le 1er novembre 2026. Article réécrit : journaux à consulter, signatures d’attaque testées sur un journal d’exemple, filtre fail2ban, limitation de débit Nginx vérifiée avec nginx -t puis en conditions réelles (réponse 429), chiffres Patchstack 2026 et cadre d’utilisation de l’IA.

Les journaux de votre serveur enregistrent chaque requête envoyée à votre site WordPress : l’adresse qui la fait, la page demandée, l’heure et la réponse. Ils ne disent pas tout, mais ils montrent la plupart des attaques automatisées avant qu’elles réussissent. Ce guide indique quels journaux ouvrir, quelles signatures chercher, quelles commandes lancer et comment répondre sans bloquer vos visiteurs.

Les exemples supposent le format « combined », celui de Nginx et d’Apache par défaut. Les commandes, en lecture seule, ont été testées sur un journal d’exemple.

Pourquoi surveiller les logs en 2026

Le rapport annuel de Patchstack, State of WordPress Security in 2026, recense 11 334 nouvelles vulnérabilités dans l’écosystème WordPress en 2025, soit 42 % de plus qu’en 2024. 91 % concernent des extensions, 9 % des thèmes, et seulement six ont été signalées dans le cœur, toutes de faible priorité. Le même rapport indique que 46 % des failles n’avaient pas de correctif au moment de leur publication, et que le délai médian pondéré avant la première exploitation des failles les plus attaquées est de cinq heures.

Une mise à jour peut donc arriver trop tard, ou ne pas exister : les logs montrent ce qui se passe pendant ce délai. Le cœur reçoit lui aussi des correctifs : WordPress 7.1.4, publiée le 28 octobre 2026, corrige douze problèmes de sécurité (vérifier qu’elle est bien installée).

Les journaux à consulter

JournalEmplacement courantCe qu’il montre
Accès Nginx ou Apache/var/log/nginx/access.log, /var/log/apache2/access.logChaque requête : IP, méthode, chemin, code, User-Agent
Erreurs Nginx ou Apache/var/log/nginx/error.log, /var/log/apache2/error.logRefus de limitation de débit, erreurs de configuration, scripts introuvables
PHP-FPMselon la configuration du poolErreurs fatales, délais dépassés, processus saturés
WordPress (debug.log)wp-content/debug.log par défautErreurs et avertissements PHP produits par le cœur, les extensions et le thème
fail2ban/var/log/fail2ban.logAdresses bannies et débannies

Sur un hébergement mutualisé, les journaux bruts sont en général dans le panneau de contrôle.

Activer le journal de WordPress sans l’exposer

La documentation de WordPress décrit trois constantes : WP_DEBUG active le débogage, WP_DEBUG_LOG écrit les erreurs dans un fichier et WP_DEBUG_DISPLAY à false évite de les afficher aux visiteurs. WP_DEBUG_LOG accepte aussi un chemin de fichier, ce qui permet de placer le journal hors du dossier public :

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', '/var/log/wordpress/exemple-debug.log' );
define( 'WP_DEBUG_DISPLAY', false );

Si vous gardez l’emplacement par défaut, wp-content/debug.log est accessible depuis le web tant que le serveur ne le bloque pas. Ce fichier contient des chemins et parfois des requêtes SQL : interdisez son accès (exemples plus bas) et désactivez le débogage une fois le diagnostic terminé.

Lire les codes de réponse sans se tromper

Une connexion échouée renvoie en général un 200 (la page de connexion est réaffichée), une connexion réussie un 302 vers le tableau de bord. Pour wp-login.php, comptez donc les POST par adresse et la proportion de 200 et de 302.

Un 403 signale une règle qui a refusé la requête. Un 429 n’apparaît que si vous l’avez configuré : par défaut, la directive limit_req de Nginx répond 503.

Les signatures d’attaque à rechercher

AttaqueCe que montre le journalFaux positifs possibles
Force brute sur wp-login.phpRafales de POST, presque tous en 200Utilisateur qui a oublié son mot de passe
Abus de xmlrpc.phpPOST répétés, souvent sans User-AgentApplication mobile, Jetpack, outil de publication
Énumération des comptes/?author=1 suivi d’une 301, /wp-json/wp/v2/usersOutils SEO, rares
Scan d’extensionsDemandes de /wp-content/plugins/<nom>/readme.txt en sérieAucun
Recherche de fichiers sensibles/.env, /.git/config, /wp-config.php.bak en 404Aucun
Injection SQL ou remontée de dossiersUNION SELECT, %27, ../ dans l’adresseRecherche interne contenant une apostrophe
Exécution dans les médiasAppel de .php sous wp-content/uploadsAucun : ce dossier ne doit pas contenir de PHP
Exploitation d’une route d’extensionPOST inhabituels vers /wp-json/<extension>/Formulaires et automatisations légitimes

Le dernier cas est le plus difficile à repérer. En avril 2025, la faille CVE-2025-3102 de l’extension SureTriggers (aujourd’hui OttoKit), présente jusqu’à la version 1.0.78 et corrigée en 1.0.79, permettait de créer un administrateur sur les sites où l’extension n’était pas configurée. Patchstack a observé les premières tentatives environ quatre heures après l’avoir ajoutée à sa base. Une rafale de POST vers une route REST inutilisée, suivie d’un nouvel administrateur, suffit à donner l’alerte.

Les commandes d’analyse

Dans le format combined, le champ 1 contient l’adresse IP, le champ 7 le chemin et le champ 9 le code de réponse. Pour une seule journée, ajoutez un filtre comme grep '31/Oct/2026'.

L=/var/log/nginx/access.log

# Adresses les plus actives
awk '{print $1}' $L | sort | uniq -c | sort -rn | head -20

# POST sur la page de connexion, par adresse et par code
grep '"POST /wp-login.php' $L | awk '{print $1, $9}' | sort | uniq -c | sort -rn | head -20

# POST sur xmlrpc.php
grep '"POST /xmlrpc.php' $L | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# Énumération des comptes
grep -E '"GET /(\?author=[0-9]+|wp-json/wp/v2/users)' $L | awk '{print $1, $7, $9}' | sort | uniq -c | sort -rn | head -20

# Scan d'extensions
grep -E '"GET /wp-content/plugins/[^/]+/readme\.txt' $L | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# Fichiers sensibles
grep -E '"(GET|POST) /(\.env|\.git/|phpmyadmin|wp-config)' $L | awk '{print $9, $1}' | sort | uniq -c | sort -rn | head -20

# PHP appelé dans les médias
grep -E '"(GET|POST) /wp-content/uploads/[^ ]*\.php' $L | awk '{print $9, $1, $7}' | head -20

# Injections dans l'adresse
grep -iE '"(GET|POST) [^"]*(union(%20|\+)+select|%27|\.\./|etc/passwd|%3Cscript)' $L | awk '{print $1, $9, $7}' | head -20

# Routes REST appelées en POST (y compris la forme ?rest_route=)
grep -E '"POST /(wp-json/|\?rest_route=)' $L | awk '{print $7, $9}' | sort | uniq -c | sort -rn | head -20

# Adresses qui accumulent les 404, puis les refus
awk '$9 == 404 {print $1}' $L | sort | uniq -c | sort -rn | head -20
awk '$9 == 403 || $9 == 429 || $9 == 503 {print $9, $1}' $L | sort | uniq -c | sort -rn | head -20

Le corps des requêtes POST n’est pas journalisé par défaut : vous voyez le volume et la cible, pas les identifiants testés ni la charge envoyée. Avant de bannir un robot qui se présente comme Googlebot, vérifiez-le par une recherche DNS inverse puis directe, comme Google le recommande.

Les réponses à mettre en place

Bannir avec fail2ban

Ce filtre repère les POST vers wp-login.php qui renvoient un 200, c’est-à-dire les échecs de connexion. Testez-le sur vos journaux avant de l’activer.

# /etc/fail2ban/filter.d/wp-login-nginx.conf
[Definition]
failregex = ^<HOST> .*"POST /wp-login\.php HTTP/[0-9.]+" 200
ignoreregex =
# /etc/fail2ban/jail.local
[wp-login]
enabled  = true
port     = http,https
filter   = wp-login-nginx
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 10m
bantime  = 1h
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wp-login-nginx.conf
sudo fail2ban-client reload
sudo fail2ban-client status wp-login

fail2ban accepte les durées abrégées (10m, 1h, 1d). Ses valeurs par défaut sont bantime = 10m, findtime = 10m et maxretry = 5. Il fournit aussi une prison nginx-limit-req, qui lit le journal d’erreurs de Nginx et bannit les adresses déjà freinées par la limitation de débit décrite ci-dessous. Si le site passe par un CDN ou un proxy inverse, configurez d’abord la récupération de l’adresse réelle (ngx_http_realip_module), sinon fail2ban bannira le proxy.

Limiter le débit sur la page de connexion

# Bloc http {}
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=5r/m;

# Bloc server {}
location = /wp-login.php {
    limit_req zone=wplogin burst=5 nodelay;
    limit_req_status 429;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

Sur notre serveur de test, dix POST envoyés en rafale ont donné sept requêtes transmises à PHP, puis trois réponses 429, chacune journalisée comme « limiting requests » dans le journal d’erreurs. Un réseau d’entreprise partagé peut être touché : surveillez les 429 après la mise en place.

Fermer xmlrpc.php ou en retirer system.multicall

La méthode system.multicall permet de tester plusieurs mots de passe dans une seule requête. Si aucun service n’utilise XML-RPC, refusez l’accès au fichier :

location = /xmlrpc.php {
    deny all;
}
location ~* ^/wp-content/uploads/.*\.php$ {
    deny all;
}
location = /wp-content/debug.log {
    deny all;
}

Sous Apache 2.4, dans le .htaccess racine :

<Files "xmlrpc.php">
    Require all denied
</Files>
<Files "debug.log">
    Require all denied
</Files>

Si une application mobile ou Jetpack en a besoin, retirez seulement la méthode concernée avec une extension « must-use » placée dans wp-content/mu-plugins/ :

<?php
/**
 * Plugin Name: Retirer system.multicall de XML-RPC
 */
add_filter( 'xmlrpc_methods', function ( $methods ) {
	unset( $methods['system.multicall'] );
	return $methods;
} );

Attention : le filtre xmlrpc_enabled ne désactive que les méthodes qui demandent une authentification. Les pingbacks restent actifs.

Ajouter un pare-feu applicatif et la double authentification

Un pare-feu applicatif (WAF) bloque les requêtes qui correspondent à des signatures connues. Patchstack, qui vend ce type de protection, rapporte que les défenses d’hébergement classiques n’ont bloqué que 12 % des attaques propres à WordPress lors d’un premier test d’intrusion, et 26 % lors d’un second, plus large. Le chiffre vient d’un acteur intéressé, mais un WAF générique ne connaît pas les routes de vos extensions. La double authentification, ou les passkeys décrites dans notre guide WebAuthn, rend inutile un mot de passe deviné.

Après une alerte : vérifier les effets

Les logs décrivent des requêtes, pas leurs conséquences. Après une alerte, contrôlez l’installation :

wp core verify-checksums
wp plugin verify-checksums --all
wp user list --role=administrator --fields=ID,user_login,user_registered
wp cron event list
find wp-content/uploads -name '*.php'

Un administrateur inconnu, un fichier du cœur modifié ou une tâche planifiée inconnue indiquent une compromission : suivez alors notre guide pour nettoyer un site WordPress piraté. Seules les extensions du dépôt WordPress.org peuvent être vérifiées par empreinte. Pour un contrôle externe, un scanner de malwares WordPress compare aussi votre installation aux checksums officiels de WordPress.org, sans rien installer sur le site.

Ce que l’IA apporte, dans un cadre précis

Pour un site, les commandes ci-dessus suffisent. L’IA devient utile pour trier plusieurs gigaoctets de journaux ou plusieurs serveurs, à trois conditions :

Le script de tri présenté dans notre guide d’analyse des logs Nginx applique ces règles. Les protections côté serveur sont détaillées dans notre guide pour protéger un serveur web. Les mêmes journaux servent aussi à la maintenance automatisée de WordPress et au contrôle des 404 après une migration WordPress. Si vous connectez un client d’IA à votre site pour créer un blog WordPress avec l’IA, surveillez aussi les appels à ses routes REST.

Questions fréquentes

Quels signaux sont les plus fiables pour repérer une attaque WordPress dans les logs ?

Le nombre de POST vers wp-login.php et xmlrpc.php par adresse, les demandes de readme.txt d’extensions en série, les accès à des fichiers sensibles et les appels de PHP dans les médias. Aucun ne prouve seul une attaque ; leur répétition depuis une même source, sur une courte période, doit retenir votre attention.

Pourquoi ne voit-on pas de code 401 lors des tentatives de connexion ?

Parce que WordPress réaffiche la page de connexion avec un message d’erreur, ce qui produit un 200. Le 401 n’apparaît qu’avec une authentification HTTP ou sur certaines routes de l’API REST.

Faut-il désactiver complètement xmlrpc.php ?

Seulement si aucun service ne l’utilise. Vérifiez d’abord dans les logs quelles requêtes légitimes y arrivent. Si une application en dépend, retirez plutôt la méthode system.multicall et limitez le débit.

Une IA peut-elle remplacer l’analyse manuelle des logs ?

Non. Elle aide à trier de grands volumes et à résumer, mais elle ne voit que ce que vous lui envoyez et peut se tromper sur l’origine d’une requête. Les mises à jour, la double authentification et la vérification des fichiers restent indispensables.

Sources

Agence web PrestaShop & WordPress

Agence web spécialisée PrestaShop et WordPress : création, maintenance, sécurité, performance et référencement. Nos tutoriels sont issus de nos interventions sur de vraies boutiques.

L'agence Genisoft → LinkedIn →