Certains jours, votre site WordPress va bien. Puis un matin votre CPU est saturé, PHP-FPM semble faire du CrossFit, et vos logs sont un défilement sans fin de POST /wp-login.php. Les utilisateurs réels ne peuvent plus se connecter. Le taux de cache s’effondre. L’attaque n’est pas « avancée », juste « implacable ».
La partie délicate n’est pas de bloquer la force brute. C’est de bloquer la force brute sans transformer votre propre connexion en un déni de service auto‑infligé. Renforcer la connexion WordPress est facile à faire de façon incorrecte et étonnamment ennuyeux à faire correctement. Faisons‑le correctement.
Faits intéressants et historique (le pourquoi de la douleur)
wp-login.phpest devenu une cible chaude parce qu’il est prévisible. Les attaquants aiment les URL stables ; WordPress a rendu l’accès admin simple et donc énumérable.XML-RPC (xmlrpc.php)a été introduit pour permettre la publication à distance. Il a aussi offert aux attaquants une méthode pour effectuer des tentatives d’authentification groupées avecsystem.multicall.- « admin » comme nom d’utilisateur était courant. Les anciennes installations WordPress l’utilisaient par défaut, et les vieilles habitudes persistent—surtout sur des sites de staging oubliés.
- La force brute est souvent une quête secondaire. Beaucoup de campagnes ne ciblent pas votre site spécifiquement ; elles balayent l’internet à la recherche de mots de passe faibles pour construire des botnets.
- Le credential stuffing dépasse la force brute. Les attaquants utilisent fréquemment des paires identifiants/mots de passe divulguées ; votre « politique de mots de passe forts » n’aide pas si les utilisateurs réutilisent leurs mots de passe ailleurs.
- La limitation de débit est plus ancienne que la plupart des frameworks web. Les ingénieurs réseau limitaient déjà les clients abusifs bien avant que « WAF » ne devienne une catégorie produit.
- Les verrouillages peuvent devenir un déni de service. Un « bloquer après 3 essais pendant 24 heures » naïf peut permettre aux attaquants de verrouiller de vrais utilisateurs en tentant leurs noms d’utilisateur.
- 403 vs 404 a une importance opérationnelle. Retourner 404 pour
/wp-login.phpà tout le monde sauf aux sources allowlistées réduit le bruit de scan et rend les logs plus faciles à comprendre. - La plupart des compromissions WordPress ne proviennent pas d’une force brute sur la connexion. Les plugins et thèmes vulnérables sont une source plus importante de compromission réelle ; le durcissement de la connexion reste nécessaire, mais pas suffisant.
Blague #1 : Les attaquants par force brute sont comme des tout‑petits devant une porte fermée—éternellement optimistes, bruyants, et convaincus que le problème vient de vous.
À quoi ressemble la force brute en production
Signaux que vous verrez en premier
Sur un petit VPS, la force brute se manifeste par des pics de charge et des workers PHP saturés. Sur un hôte plus grand, c’est plus subtil : augmentation des 499/504, TTFB élevé, et latence sur l’endpoint de connexion. Le symptôme visible pour l’utilisateur est généralement « Je ne peux pas me connecter » ou « Le site est lent », mais le vrai problème est que chaque tentative de connexion déclenche des chemins de code coûteux : hachage de mot de passe, création de session, accès base de données, peut‑être des hooks de plugins qui ne devraient pas être sur le chemin de la requête.
Où les attaquants frappent
/wp-login.php— formulaire de connexion classique./wp-admin/— redirige vers la connexion ; génère quand même du travail./xmlrpc.php— endpoint de publication à distance, souvent abusé pour deviner des mots de passe.- Endpoints REST qui divulguent des noms d’utilisateur — ce n’est pas un contournement d’auth, mais ça aide à l’énumération.
Pourquoi « bloquer simplement les IP » échoue
Les attaques sont distribuées. Les IP tournent. Certaines viennent de réseaux résidentiels. D’autres de fournisseurs cloud. Si votre stratégie de défense exige des blocages d’IP manuels, vous ne défendez pas—vous faites de l’archéologie de logs.
Votre véritable objectif
Vous essayez de réduire les tentatives d’authentification à un taux faible et prévisible tout en garantissant que vous conservez au moins deux moyens indépendants de récupérer l’accès admin. C’est cette deuxième partie qui fait échouer la plupart des gens.
Une citation opérationnelle à garder sur un post‑it : « L’espoir n’est pas une stratégie. » — Gene Kranz
Playbook de diagnostic rapide (premier/deuxième/troisième)
Ceci est la version « vous êtes en astreinte, il est 02:00, et quelqu’un du marketing est réveillé ».
Première étape : confirmez que c’est une force brute et identifiez le point d’entrée
- Vérifiez les logs d’accès pour les chemins principaux et les taux de requêtes.
- Confirmez s’il s’agit de
wp-login.php,xmlrpc.php, ou des deux. - Cherchez une boucle serrée : même URI, même méthode, beaucoup de 200/302/403.
Deuxième étape : protégez la capacité avant de poursuivre l’élégance
- Ajoutez une limitation de débit temporaire à la périphérie (CDN/WAF) ou dans Nginx/Apache.
- Si nécessaire, bloquez
xmlrpc.phptemporairement et validez qu’aucune fonctionnalité critique ne casse. - Augmentez la clarté des logs : séparez les endpoints d’auth dans leur propre format ou fichier de log.
Troisième étape : implémentez des contrôles durables avec une voie de récupération
- Allowlistez les IP admin (ou mieux : exigez VPN / accès zero‑trust) pour
/wp-admin/et/wp-login.php. - Activez la 2FA pour les comptes administrateurs.
- Installez Fail2ban (ou équivalent) piloté par les logs web.
- Désactivez ou restreignez XML‑RPC et confirmez les workflows Jetpack/mobile si vous les utilisez.
Le goulot d’étranglement n’est généralement pas « le CPU est lent ». C’est « votre chemin d’auth n’est pas limité ». Réparez cela en premier ; ensuite optimisez.
Tâches pratiques : commandes, sorties et décisions
Ces tâches supposent Linux avec Nginx ou Apache, plus systemd. Exécutez‑les sur l’hôte web (ou sur le reverse proxy si vous en avez un). Chaque tâche inclut : commande, sortie exemple, ce que cela signifie, et la décision à prendre.
Tâche 1 : Identifier rapidement les chemins de requête principaux
cr0x@server:~$ sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
48219 /wp-login.php
11703 /xmlrpc.php
4122 /
3088 /wp-admin/
944 /wp-json/wp/v2/users
Ce que la sortie signifie : /wp-login.php domine. /xmlrpc.php est aussi fortement sollicité. L’endpoint REST users suggère une énumération de noms d’utilisateur.
Décision : Priorisez la limitation de débit et le contrôle d’accès sur wp-login.php et xmlrpc.php. Envisagez de restreindre l’exposition des endpoints REST pour les utilisateurs.
Tâche 2 : Mesurer le taux de requêtes vers wp-login.php
cr0x@server:~$ sudo awk '$7=="/wp-login.php"{print $4}' /var/log/nginx/access.log | cut -d: -f1-3 | sort | uniq -c | sort -nr | head
982 [27/Dec/2025:01
944 [27/Dec/2025:00
901 [26/Dec/2025:23
Ce que cela signifie : Environ 900–980 hits de connexion par heure (ou par minute, selon la granularité des logs). Quoi qu’il en soit : beaucoup trop pour un site réel.
Décision : Fixez un plafond initial (exemple : 5 requêtes/minute par IP pour wp-login.php) et observez les effets secondaires.
Tâche 3 : Trouver les IP les plus offensantes
cr0x@server:~$ sudo awk '$7=="/wp-login.php"{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
710 203.0.113.44
688 198.51.100.72
621 192.0.2.19
Ce que cela signifie : Quelques IP sont bruyantes. Beaucoup d’attaques ne sont pas si concentrées, mais quand elles le sont, vous pouvez gagner du temps avec des blocages temporaires.
Décision : Utilisez un blocage firewall court uniquement comme palliatif. Ne construisez pas un processus autour du blocage manuel.
Tâche 4 : Vérifier si XML‑RPC est abusé via multicall
cr0x@server:~$ sudo grep -c "POST /xmlrpc.php" /var/log/nginx/access.log
11703
Ce que cela signifie : L’endpoint est actif. Pour confirmer un multicall, vous inspecteriez les corps de requête au WAF/proxy ou activeriez la journalisation limitée du corps de la requête (avec prudence ; cela peut divulguer des identifiants).
Décision : Si vous n’avez pas explicitement besoin de XML‑RPC, désactivez‑le. Si vous en avez besoin, restreignez‑le fortement.
Tâche 5 : Chercher un mélange 401/403/200 pour comprendre ce qui se passe
cr0x@server:~$ sudo awk '$7=="/wp-login.php"{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr
31001 200
14112 302
1106 403
Ce que cela signifie : Beaucoup de chargements de page de connexion retournent 200. Les 302 indiquent des redirections (souvent des redirections après connexion réussie, mais aussi des redirections de retour vers la page de connexion en cas d’échec selon la configuration).
Décision : Ne devinez pas. Utilisez les logs applicatifs ou les logs d’auth WordPress (plugin) si vous avez besoin de comptes précis de « mots de passe échoués » ; sinon, limitez le débit quoi qu’il en soit.
Tâche 6 : Confirmer la pression sur PHP‑FPM (un goulot courant)
cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2025-12-26 23:12:08 UTC; 2h 11min ago
Main PID: 1240 (php-fpm8.2)
Status: "Processes active: 43, idle: 2, Requests: 184201, slow: 57, Traffic: 1.2req/sec"
Ce que cela signifie : Vous êtes proche du nombre maximal de workers actifs ; il existe des requêtes lentes. La force brute peut épuiser les workers et priver le trafic réel.
Décision : Limitez immédiatement les endpoints d’auth. Ensuite seulement, envisagez d’ajuster FPM.
Tâche 7 : Inspecter les logs d’erreur Nginx pour des timeouts upstream
cr0x@server:~$ sudo tail -n 8 /var/log/nginx/error.log
2025/12/27 01:18:21 [error] 2011#2011: *9921 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 198.51.100.72, server: example.com, request: "POST /wp-login.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "example.com"
Ce que cela signifie : Le chemin d’auth est suffisamment lent pour atteindre des timeouts—classique sous force brute. De plus, les timeouts créent des retries, ce qui aggrave la charge.
Décision : Limitez, puis réduisez le travail dans le chemin d’auth (plugins, appels externes), puis envisagez d’ajuster les timeouts.
Tâche 8 : Ajouter une limitation Nginx chirurgicale pour wp-login.php
D’abord, vérifiez votre layout d’inclusions Nginx. Ensuite implémentez quelque chose de minimal et réversible.
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -n "http {" -n | head
32:http {
Ce que cela signifie : Vous savez où se trouve le contexte http ; les zones de limit_req doivent être définies là.
Décision : Ajoutez un limit_req_zone sous http, puis appliquez‑le dans le location pour wp-login.php.
cr0x@server:~$ sudo bash -lc 'cat >/etc/nginx/conf.d/wp-login-rate-limit.conf <<"EOF"
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=5r/m;
EOF'
cr0x@server:~$ sudo bash -lc 'cat >/etc/nginx/snippets/wp-login-protect.conf <<"EOF"
location = /wp-login.php {
limit_req zone=wp_login burst=10 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
EOF'
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Ce que cela signifie : La configuration parse. Vous n’avez pas encore prouvé le comportement, mais vous pouvez recharger en toute sécurité.
Décision : Rechargez et surveillez les 429 et les plaintes des utilisateurs.
cr0x@server:~$ sudo systemctl reload nginx
Tâche 9 : Vérifier que la limitation est effective
cr0x@server:~$ sudo grep -E "limiting requests" /var/log/nginx/error.log | tail -n 3
2025/12/27 01:22:09 [error] 2011#2011: *10441 limiting requests, excess: 5.900 by zone "wp_login", client: 203.0.113.44, server: example.com, request: "POST /wp-login.php HTTP/1.1", host: "example.com"
Ce que cela signifie : Nginx throttle activement. C’est une protection de capacité.
Décision : Gardez‑la, mais ne vous arrêtez pas là—la limitation seule n’empêchera pas indéfiniment le credential stuffing lent.
Tâche 10 : Ajouter une porte allowlist sûre pour wp-admin (sans casser le front‑end)
Ce patron est brutal et efficace : n’autoriser l’accès admin qu’à partir d’un IP de sortie VPN ou du NAT de l’entreprise. Ce n’est pas convivial pour les utilisateurs nomades, mais c’est opérationnellement raisonnable pour les entreprises.
cr0x@server:~$ sudo bash -lc 'cat >/etc/nginx/snippets/wp-admin-allowlist.conf <<"EOF"
location ^~ /wp-admin/ {
allow 203.0.113.10;
allow 203.0.113.11;
deny all;
try_files $uri $uri/ /index.php?$args;
}
EOF'
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Ce que cela signifie : Syntaxe ok. Rappelez‑vous que /wp-admin/admin-ajax.php est utilisé par les thèmes/plugins côté front‑end.
Décision : Exemptez explicitement admin-ajax.php de l’allowlist si votre site l’utilise publiquement.
cr0x@server:~$ sudo bash -lc 'cat >/etc/nginx/snippets/wp-admin-ajax-open.conf <<"EOF"
location = /wp-admin/admin-ajax.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
EOF'
Tâche 11 : Désactiver XML‑RPC au niveau web (rapide) et confirmer l’impact
cr0x@server:~$ sudo bash -lc 'cat >/etc/nginx/snippets/disable-xmlrpc.conf <<"EOF"
location = /xmlrpc.php {
return 403;
}
EOF'
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Ce que cela signifie : Cela bloque complètement XML‑RPC. Jetpack, certaines apps mobiles et certaines intégrations peuvent en dépendre.
Décision : Si vous avez besoin de XML‑RPC, ne le désactivez pas—restreignez‑le à des IP connues ou exigez des contrôles additionnels. Sinon, laissez‑le désactivé.
Tâche 12 : Installer et valider Fail2ban contre les patterns wp-login
cr0x@server:~$ sudo apt-get update -y && sudo apt-get install -y fail2ban
Reading package lists... Done
Building dependency tree... Done
fail2ban is already the newest version (1.0.2-2).
Ce que cela signifie : Fail2ban est disponible. Il vous faut maintenant un filtre et un jail ajustés à votre format de logs.
Décision : Utilisez un temps de ban court et un findtime raisonnable ; vous limitez déjà le débit, ceci vise les récidivistes et la réduction du bruit distribué.
cr0x@server:~$ sudo bash -lc 'cat >/etc/fail2ban/filter.d/nginx-wp-login.conf <<"EOF"
[Definition]
failregex = ^<HOST> - .* "POST /wp-login\.php HTTP/1\.[01]" (200|302) .*
ignoreregex =
EOF'
cr0x@server:~$ sudo bash -lc 'cat >/etc/fail2ban/jail.d/nginx-wp-login.local <<"EOF"
[nginx-wp-login]
enabled = true
port = http,https
filter = nginx-wp-login
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 600
bantime = 3600
EOF'
cr0x@server:~$ sudo fail2ban-client reload
OK
cr0x@server:~$ sudo fail2ban-client status nginx-wp-login
Status for the jail: nginx-wp-login
|- Filter
| |- Currently failed: 2
| |- Total failed: 118
| `- File list: /var/log/nginx/access.log
`- Actions
|- Currently banned: 3
|- Total banned: 7
`- Banned IP list: 198.51.100.72 203.0.113.44 192.0.2.19
Ce que cela signifie : Vous avez des bans automatisés. Ce n’est pas « la sécurité résolue », mais cela réduit significativement le bruit.
Décision : Gardez des bans modérés ; n’interdisez pas par accident le NAT du bureau pendant une heure parce que quelqu’un a oublié son mot de passe.
Tâche 13 : Confirmer les utilisateurs WordPress et supprimer les risques évidents
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp user list --fields=ID,user_login,roles
+----+------------+----------------------+
| ID | user_login | roles |
+----+------------+----------------------+
| 1 | admin | administrator |
| 12 | editor1 | editor |
| 17 | seo-team | administrator |
+----+------------+----------------------+
Ce que cela signifie : Si vous avez encore admin comme login, vous facilitez la tâche des attaquants.
Décision : Créez un nouveau compte admin avec un login non évident, migrez les privilèges, puis supprimez ou rétrogradez admin. (Et activez la 2FA.)
Tâche 14 : Récupération d’urgence — réinitialiser un mot de passe admin en toute sécurité
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp user update admin --user_pass='REDACTED-strong-password'
Success: Updated user 1.
Ce que cela signifie : Vous pouvez regagner l’accès sans toucher à phpMyAdmin ou modifier la base de données manuellement.
Décision : Si vous n’avez pas WP‑CLI installé, installez‑le maintenant—avant d’en avoir besoin à 02:00.
Tâche 15 : Vérifier que vous ne vous êtes pas verrouillé (le test ennuyeux mais nécessaire)
cr0x@server:~$ curl -I -s https://example.com/wp-login.php | head -n 5
HTTP/2 200
date: Sat, 27 Dec 2025 01:28:11 GMT
content-type: text/html; charset=UTF-8
cache-control: no-store, no-cache, must-revalidate, max-age=0
Ce que cela signifie : L’endpoint est accessible depuis l’endroit où vous testez. Si vous avez appliqué des allowlists, testez depuis un réseau autorisé également.
Décision : Faites un second test depuis une IP non autorisée (ou demandez à un collègue sur LTE) pour confirmer que vous obtenez le comportement de refus prévu.
Patrons de durcissement qui ne vous verrouillent pas
1) Privilégiez les contrôles « porte » plutôt que les contrôles « deviner »
La limitation de débit et Fail2ban réagissent au comportement. Les allowlists et les exigences de VPN empêchent l’exposition entièrement. Si c’est un admin WordPress d’entreprise utilisé par des employés, le bon choix est simple : ne pas exposer wp-admin à l’internet public. Placez‑le derrière un VPN, un proxy conscient de l’identité, ou au minimum une allowlist IP.
Si vous gérez un site communautaire avec de nombreux admins mobiles, vous ne pouvez pas compter sur l’allowlisting. Alors misez davantage sur : 2FA, authentification sans mot de passe (si disponible), et une limitation de débit stricte.
2) Rendez wp-login peu coûteux à solliciter
Même avec des limites de débit, vous voulez que chaque requête coûte le moins possible.
- Laissez le chemin de connexion exempt de plugins lourds qui se greffent à l’authentification et appellent des API externes.
- Assurez‑vous que le cache d’objets fonctionne pour réduire la charge sur la base de données lors de requêtes répétées.
- Désactivez les endpoints d’énumération des utilisateurs (si possible) pour que les attaquants ne puissent pas confirmer les noms d’utilisateur à moindre coût.
3) Décidez explicitement quoi faire avec XML‑RPC
XML‑RPC n’est pas « maléfique ». Il est juste ancien et souvent inutile. Si vous n’utilisez pas Jetpack, l’application mobile WordPress ou des intégrations legacy, bloquez‑le.
Si vous en avez besoin, restreignez‑le :
- Allowlistez seulement des IP connues (si votre intégration a une egress stable).
- Limitez son débit séparément de
wp-login.php. - Privilégiez des contrôles applicatifs qui désactivent l’abus de
system.multicall(certains plugins de sécurité offrent cela).
4) Utilisez la 2FA sérieusement
La 2FA n’empêche pas les tentatives de force brute ; elle rend les succès beaucoup moins probables. Le gain opérationnel est énorme : vous pouvez être un peu moins agressif avec les verrouillages sans augmenter le risque.
Deux règles opérationnelles :
- Exigez la 2FA pour les comptes administrateurs au minimum.
- Conservez les codes de secours dans votre gestionnaire de mots de passe accessibles à la rotation d’astreinte.
5) Ne « cachez » pas wp-login comme défense principale
Changer l’URL de connexion peut réduire le bruit. Ça ne corrige pas le problème sous‑jacent, et ça peut casser des intégrations, des caches et des runbooks opérationnels. Ne l’utilisez qu’en mesure secondaire, et seulement si vous avez un plan de récupération solide.
6) Donnez‑vous au moins deux voies de récupération
Les verrouillages sont survivables si vous les avez planifiés. Choisissez deux (ou plus) de ces options et testez‑les trimestriellement :
- Accès WP‑CLI en tant que
www-data(ou l’utilisateur FPM approprié) pour réinitialiser les mots de passe et gérer les plugins. - Profil VPN « break‑glass » d’urgence stocké dans un coffre sécurisé.
- Un bastion avec IP stable allowlistée pour les endpoints admin.
- Procédure d’accès à la base de données pour créer un utilisateur admin (dernier recours ; dangereux en urgence).
Blague #2 : La seule chose plus facile que de verrouiller les attaquants, c’est de verrouiller votre PDG cinq minutes avant une présentation au conseil.
Trois mini‑histoires d’entreprise (comment on échoue réellement)
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
L’entreprise A avait une installation WordPress « qui n’était pas importante ». Elle hébergeait un blog carrières et quelques landing pages. Le produit principal était ailleurs, donc l’hôte WordPress vivait dans un coin négligé de l’infrastructure.
Un ingénieur a remarqué des tentatives de force brute et a ajouté une règle agressive : bloquer toute IP qui touche /wp-login.php plus de trois fois par jour. Ça semblait sûr ; « les vrais utilisateurs ne ratent pas leur mot de passe autant », se sont‑ils dit. La mauvaise hypothèse n’était pas technique, elle était humaine. Les vrais utilisateurs ratent absolument leur mot de passe autant, surtout après une migration SSO, un changement de gestionnaire de mots de passe, ou de longues vacances.
L’attaquant n’avait pas besoin de deviner des mots de passe. Il avait juste besoin d’une liste de noms d’utilisateur. Il a tenté trois logins pour chaque nom d’admin connu depuis un ensemble d’IP rotatives. Le plugin de sécurité a verrouillé chaque compte admin pendant 24 heures. Soudain personne ne pouvait publier d’offres d’emploi, et les recruteurs envoyaient des captures d’écran de bannières « compte verrouillé » comme s’il s’agissait d’un bug produit.
La correction a nécessité un rollback de la politique de verrouillage, des déverrouillages manuels, et une nouvelle conception : limitation par IP (pas par nom d’utilisateur), fenêtres de ban modestes, 2FA, et accès admin derrière un VPN. La leçon importante était simple : la défense contre la force brute doit punir les bots, pas les humains.
Mini‑histoire 2 : L’optimisation qui a échoué
L’entreprise B faisait tourner WordPress derrière un reverse proxy et voulait réduire la charge backend. Quelqu’un a activé le caching de pages complet agressivement et a essayé de « tout mettre en cache ». Le site est devenu plus rapide—jusqu’à la prochaine vague de force brute.
La couche de cache ne mettait pas en cache wp-login.php (bien), mais elle mettait en cache certaines pages de redirection et d’erreur d’une manière étrange. Sous charge, les clients recevaient des redirections incohérentes entre /wp-admin/ et /wp-login.php. Certains utilisateurs restaient coincés en boucle. Pendant ce temps, les clés de cache du proxy n’incluaient pas un header qui distinguait certaines réponses liées à l’auth. Le proxy servait rapidement la mauvaise chose.
Ça a empiré : le proxy avait été configuré pour relancer automatiquement les erreurs upstream. Quand PHP‑FPM a commencé à timeout sous la force brute, le proxy a relancé. Cela a doublé le trafic sur l’endpoint le plus chaud. Tout le monde avait de bonnes intentions ; le système s’en fichait.
La correction a été « moins malin, plus correct » : exclure explicitement le caching pour tous les chemins d’auth, désactiver les relances automatiques pour les requêtes non idempotentes comme POST vers la connexion, et implémenter des limites de débit au niveau du proxy. La performance est revenue, et le comportement est redevenu prévisible. La morale : le caching n’est pas un contrôle de sécurité, et les relances ne sont pas gratuites.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
L’entreprise C avait une habitude banale : chaque changement d’infrastructure pour WordPress incluait une checklist de récupération testée. Pas une page wiki que personne ne lit—un vrai runbook avec un dry‑run chaque trimestre. Deux admins faisaient l’exercice : « simuler un verrouillage », « récupérer l’accès via WP‑CLI », « valider les backups 2FA », « confirmer que l’allowlist fonctionne », « confirmer que le trafic non‑admin n’est pas affecté ».
Quand une nouvelle règle WAF a été déployée globalement, elle a commencé à challenger les connexions avec un contrôle anti‑bot qui ne jouait pas bien avec certains réseaux d’entreprise. Les utilisateurs se sont plaints que la connexion « tourne indéfiniment ». L’équipe WAF a initialement suspecté l’hôte WordPress, parce que c’est souvent le cas.
L’ingénieur d’astreinte a suivi le runbook. D’abord, valider la santé du backend. Deuxièmement, vérifier les codes de statut des endpoints d’auth. Troisièmement, comparer le comportement depuis un bastion autorisé et depuis un client normal. La différence a pointé directement vers le WAF. Ils ont rollbacké la règle spécifique pour wp-login.php tout en gardant les limites de débit et les protections bot pour le reste du site.
Pas d’héroïsme. Pas de suppositions. Juste une voie de récupération pratiquée et un périmètre contrôlé. La partie « ennuyeuse »—les drills routiniers—a exactement permis d’éviter une nuit à débattre des règles firewall sur Slack.
Erreurs courantes : symptômes → cause racine → correctif
1) Symptom : Les admins ne peuvent plus se connecter au hasard ; les tickets support explosent
Cause racine : Verrouillage par nom d’utilisateur (ou verrouillages globaux trop stricts) permet aux attaquants de verrouiller des comptes légitimes sans connaître les mots de passe.
Correctif : Limitez le débit par IP et par endpoint, pas par nom d’utilisateur. Raccourcissez les bans. Exigez la 2FA pour les admins. Utilisez un chemin d’accès admin allowlisté (VPN/bastion).
2) Symptom : Le site est lent ; nombre max d’enfants PHP‑FPM atteint
Cause racine : Des tentatives de connexion non throttlées épuisent les workers PHP ; des hooks d’auth coûteux amplifient le coût.
Correctif : Ajoutez une limitation au niveau web (limit_req ou équivalent), puis auditez les plugins pour les hooks d’auth et retirez les lourds du chemin de connexion.
3) Symptom : Bloquer /wp-admin/ casse des fonctions front‑end
Cause racine : /wp-admin/admin-ajax.php est utilisé par des composants publics du site ; bloquer tout /wp-admin/ casse les features AJAX.
Correctif : Autorisez l’accès public à admin-ajax.php (et à tout autre endpoint requis) tout en restreignant le reste de /wp-admin/.
4) Symptom : Vous avez désactivé XML‑RPC et quelque chose a « mystérieusement » cessé de fonctionner
Cause racine : Jetpack, les apps mobiles, ou des intégrations legacy dépendaient de XML‑RPC.
Correctif : Soit réactivez‑le avec allowlisting IP/limitation de débit, soit remplacez l’intégration par des alternatives basées sur REST. Faites de XML‑RPC un choix conscient.
5) Symptom : Vous avez ajouté un challenge WAF et les connexions bouclent ou échouent silencieusement
Cause racine : Les challenges bot peuvent casser les flux POST, les cookies, ou les clients non‑navigateur ; parfois les proxies d’entreprise suppriment des headers.
Correctif : Exemptez /wp-login.php des challenges interactifs ; utilisez des limites de débit et des règles de réputation à la place. Testez depuis plusieurs réseaux.
6) Symptom : Fail2ban bannit tout le monde derrière un NAT
Cause racine : Beaucoup d’utilisateurs partagent une IP publique ; le maxretry se déclenche sur l’agrégat.
Correctif : Augmentez les seuils, raccourcissez les bans, et fiez‑vous davantage à la limitation de débit. Envisagez de placer l’accès admin derrière un VPN pour réduire les collisions d’IP partagées.
7) Symptom : Vous avez « caché » wp-login et maintenant des intégrations échouent et personne ne se souvient de l’URL
Cause racine : Sécurité par l’obscurité sans hygiène opérationnelle.
Correctif : Si vous devez changer l’URL de connexion, documentez‑la dans un runbook interne et gardez un chemin break‑glass (WP‑CLI + VPN/bastion). Traitez‑le comme un réducteur de bruit optionnel, pas comme une défense.
Checklists / plan étape par étape
Phase 0 (aujourd’hui) : arrêter l’hémorragie sans casser les choses
- Confirmez les endpoints ciblés dans les logs :
wp-login.php,xmlrpc.php,wp-admin. - Ajoutez une limitation au niveau web sur
wp-login.php(etxmlrpc.phpsi activé). - Désactivez XML‑RPC si vous n’en avez pas besoin. Si vous en avez besoin, restreignez‑le plutôt que de le laisser ouvert.
- Vérifiez la saturation PHP‑FPM et assurez‑vous que les timeouts ne provoquent pas de relances en cascade.
- Vérifiez que la connexion admin réelle fonctionne toujours depuis au moins deux réseaux.
Phase 1 (cette semaine) : réduire l’exposition et ajouter des contrôles d’identité
- Mettez wp-admin derrière une porte : VPN, proxy conscient de l’identité, ou allowlist IP.
- Exigez la 2FA pour les admins et stockez les codes de secours dans un coffre partagé et contrôlé.
- Supprimez les comptes évidents : supprimez
admin, auditez les admins dormants, appliquez le principe du moindre privilège. - Déployez Fail2ban adapté à votre format de logs et aux réalités NAT.
- Ajoutez de la supervision pour les pics sur les endpoints d’auth, les 429, et l’épuisement des workers FPM.
Phase 2 (ce mois) : rendre ça résilient et testable
- Créer un runbook break‑glass : réinitialisation WP‑CLI, désactivation de plugin, étapes de rollback WAF.
- Faire un exercice de verrouillage : simuler une défaillance d’allowlist et récupérer.
- Revoir la surface d’attaque plugins/thèmes ; mettre à jour, supprimer l’abandonware, et réduire les hooks d’auth.
- Envisager de placer les endpoints d’auth derrière une protection séparée (règles edge, limites plus strictes, logging dédié).
FAQ
1) Dois‑je simplement bloquer complètement wp-login.php ?
Si vous avez un chemin admin contrôlé (VPN/bastion/proxy d’identité), oui—bloquez‑le pour l’internet public et n’autorisez que la porte. Si vos admins sont nomades, ne bloquez pas complètement ; limitez le débit + 2FA + Fail2ban.
2) Changer l’URL de connexion en vaut‑il la peine ?
Ça réduit le bruit, pas le risque. Ne le faites qu’après avoir mis en place la limitation de débit, la 2FA, et un plan de récupération testé. Sinon vous échangez un problème contre un incident « où est la connexion ».
3) Pourquoi je vois de la force brute même sur un petit site obscur ?
Parce que c’est automatisé. Les attaquants n’ont pas besoin de savoir que vous existez ; ils ont juste besoin de savoir que WordPress existe. Les endpoints prévisibles sont leurs préférés.
4) La 2FA arrêtera‑t‑elle les attaques par force brute ?
Non. Elle empêche beaucoup de connexions réussies. Vous avez toujours besoin de limitation de débit pour protéger la capacité et éviter que l’auth ne devienne un déni de service.
5) Puis‑je désactiver XML‑RPC en toute sécurité ?
Souvent oui. Mais vérifiez si vous utilisez Jetpack, l’application mobile WordPress, ou une intégration ancienne. Si vous n’êtes pas sûr, désactivez‑le pendant une fenêtre à faible trafic et surveillez les logs et les workflows métier.
6) Pourquoi bloquer /wp-admin/ casse parfois la page d’accueil ?
Ça casse généralement des fonctionnalités qui appellent /wp-admin/admin-ajax.php depuis le front‑end. Exemptez cet endpoint (ou migrez loin d’admin-ajax si possible).
7) Fail2ban suffit‑il à lui seul ?
Non. C’est une bonne seconde ligne. La première ligne est la limitation au niveau web et la réduction de l’exposition. Fail2ban a aussi du mal avec les attaques très distribuées et les environnements NAT partagés.
8) Comment éviter de me verrouiller en ajoutant des allowlists ?
Gardez toujours une seconde méthode d’accès : un bastion avec IP stable, ou un VPN. Validez depuis des réseaux autorisés et non autorisés avant de partir. Et gardez WP‑CLI prêt pour réinitialiser des identifiants.
9) Quelle est une limite de débit raisonnable pour wp-login.php ?
Commencez autour de 5 requêtes/minute par IP avec un petit burst, puis ajustez. Si vous avez beaucoup d’utilisateurs légitimes derrière un NAT, augmentez le burst ou le taux et fiez‑vous davantage à la 2FA et à une couche de réputation WAF.
10) Et si l’attaquant utilise des identifiants valides (credential stuffing) ?
La limitation de débit réduit la vitesse, la 2FA empêche la plupart des détournements de comptes, et la supervision (logins échoués, géos inhabituels, voyages impossibles) détecte ce qui passe. En outre : forcez l’utilisation d’un gestionnaire de mots de passe et supprimez les comptes dormants.
Prochaines étapes que vous pouvez faire aujourd’hui
Faites trois choses et votre connexion WordPress cesse d’être un punching‑ball :
- Limiter les endpoints d’auth au niveau web. Cela protège immédiatement la capacité.
- Placer wp-admin derrière une porte : VPN/bastion/proxy d’identité si c’est un admin d’entreprise, pas un admin communautaire public.
- Conserver des chemins de récupération réels : accès WP‑CLI, codes de secours 2FA, et un runbook testé. « Nous pouvons toujours SSHer » n’est pas un plan de récupération ; c’est une histoire pour s’endormir.
Puis prenez la victoire peu glamour : auditez les comptes admin, supprimez les noms d’utilisateur évidents, désactivez ce que vous n’utilisez pas (surtout XML‑RPC), et surveillez les taux de requêtes de connexion comme vous surveillez l’espace disque. Pas parce que c’est excitant. Parce que ça marche.