Rien ne gâche une astreinte calme comme une capture d’écran du PDG montrant « 403 Forbidden » sur la page de connexion WordPress. L’équipe marketing ne peut pas publier. Les éditeurs ne peuvent pas téléverser. Les clients ne peuvent pas finaliser un achat. Et votre WAF, qui est censé être l’adulte discret dans la pièce, est devenu le videur qui expulse les invités.
La solution n’est pas « désactiver le WAF » ni « mettre en liste blanche tout /wp-admin/ ». La solution consiste à recueillir des preuves, isoler la règle exacte, et ajuster la plus petite exception possible avec des garde-fous. Vous voulez que WordPress fonctionne et que les attaquants s’ennuient. Les deux sont atteignables.
Ce qui se passe réellement quand WordPress est « bloqué par le WAF »
Quand on dit « le WAF a bloqué WordPress », cela signifie généralement l’une des quatre choses suivantes :
- Un vrai positif : quelqu’un (ou quelque chose) tente réellement une injection SQL/XSS/exécution distante et le WAF l’a bloqué. WordPress n’est que la cible où le trafic a atterri.
- Un faux positif : un comportement légitime de WordPress ressemble à une attaque. Les coupables habituels : HTML de l’éditeur, requêtes de l’API REST, points de terminaison AJAX, téléversements de fichiers, ou pages d’administration de plugins avec des paramètres étranges.
- Un déclencheur de contrôle de taux/comportement : protection contre les bots, protection des connexions, ou limitation de débit générique se déclenche sur l’activité d’administration, les tâches cron, ou une vérification de santé CDN.
- Un décalage des frontières de confiance : l’application voit des adresses IP clientes différentes de celles que voit le WAF, ou des cookies/en-têtes sont modifiés par un proxy. Une règle destinée à protéger une couche punit une autre.
La plupart des problèmes WordPress/WAF proviennent de la concentration des points de terminaison. WordPress canalise beaucoup de comportements complexes via quelques URL :
/wp-login.phppour l’authentification/wp-admin/admin-ajax.phppour le comportement UI asynchrone (front-end et back-end)/wp-json/pour l’API REST (y compris les fonctionnalités de l’éditeur Gutenberg)/xmlrpc.phphistoriquement utilisé pour la publication à distance ; aujourd’hui surtout utilisé par des bots et de vieilles intégrations/wp-admin/pour tout le reste qui a l’air « admin » et donc « attaqué »
Les WAF ne « comprennent » pas WordPress. Ils comprennent les requêtes HTTP et des motifs qui souvent corrèlent avec des attaques. WordPress génère du contenu et des paramètres qui ressemblent accidentellement à des attaques. Votre rôle est de réduire l’écart sans enlever une protection significative.
Une idée directrice, paraphrasant John Allspaw : l’exploitation consiste à permettre le changement sans se blesser. Cela s’applique aussi aux WAF — les exceptions sûres sont une forme de maturité opérationnelle.
Mode d’emploi pour un diagnostic rapide (premier/deuxième/troisième)
Premier : confirmer que c’est le WAF (pas l’application, pas le CDN, pas l’auth)
- Regardez le statut HTTP : 403/406/429 sont des résultats fréquents d’un WAF, mais les applications renvoient aussi du 403.
- Vérifiez la présence d’en-têtes WAF (spécifiques au fournisseur) et d’un ID de requête que vous pouvez tracer.
- Corrélez l’horodatage et l’IP cliente entre les logs CDN/proxy et les logs d’origine.
Deuxième : identifier la règle exacte et le composant de requête exact
- Récupérez l’entrée d’événement/audit du WAF : ID/groupe de règle, variable correspondant (ARGS, REQUEST_URI, REQUEST_HEADERS), extrait de la charge correspondante.
- Déterminez si la correspondance concerne l’URI, un paramètre de requête, le body, un cookie ou un en-tête.
- Confirmez si la règle est en mode block ou en mode log/count.
Troisième : choisir le levier d’ajustement le moins dangereux
- Corriger l’application/la requête si elle est vraiment étrange (plugin défectueux, encodage cassé, cookies énormes).
- Restreindre le périmètre en utilisant URI + méthode + état d’authentification (administrateur uniquement) avant d’envisager de désactiver une règle.
- Exclure la plus petite variable (un seul paramètre) plutôt qu’un groupe de règles entier.
- Augmenter les seuils d’anomalie seulement quand vous pouvez prouver que le trafic est sûr et que vous avez des contrôles compensatoires.
Faits et contexte intéressants (parce que l’histoire se répète)
- ModSecurity est né au début des années 2000 comme module Apache — les WAF étaient à l’origine un « correctif chaud » pour des applications vulnérables, pas une panacée.
- L’OWASP Core Rule Set (CRS) est devenu le cerveau « générique » par défaut pour de nombreuses piles ; il est conçu pour être large, pas spécifique à WordPress.
- L’endpoint XML-RPC de WordPress était important à la fin des années 2000 pour la publication à distance ; aujourd’hui il est souvent abusé pour des attaques en force brute et des modèles d’amplification.
- Gutenberg (l’éditeur de blocs) a déplacé WordPress vers une utilisation plus intensive de l’API REST ; de nombreux problèmes WAF « soudains » sont apparus après des mises à jour de l’éditeur parce que la forme des requêtes a changé.
- Les premières déploiements de WAF tournaient souvent en « detect only » pendant des semaines ; bloquer immédiatement causait des pannes. Les équipes matures traitent encore les changements de WAF comme du code.
- Les modèles de sécurité « positive » (autoriser des motifs connus) précèdent le marketing WAF moderne ; ils sont plus difficiles à gérer, mais plus robustes pour les endpoints d’administration.
- Certaines signatures de détection SQLi datent de plusieurs décennies ; les attaquants s’adaptent, mais les applications aussi — les faux positifs proviennent souvent de corps JSON modernes déclenchant des règles anciennes.
- Les CDN ont popularisé les règles WAF gérées et rendu courant le « WAF devant tout » ; l’avantage est l’échelle, l’inconvénient est que vous déboguez maintenant un moteur de politique distribué.
Principes pour un ajustement sûr du WAF (pas de théâtre de sécurité)
1) Ne négociez pas avec des 403 ; collectez des preuves
Chaque produit WAF a un moyen de vous indiquer quelle règle s’est déclenchée et ce qu’elle a matché. Si vous ne pouvez pas voir ça, vous opérez à l’aveugle. Quand les parties prenantes demandent une réparation instantanée, donnez-leur un triage immédiat : « Je peux arrêter l’hémorragie en contournant le WAF pour ce chemin, mais j’ai besoin de 30 minutes pour le faire en sécurité. » Ensuite faites-le en sécurité.
2) Les exceptions doivent être plus étroites que votre patience
Les bonnes exceptions sont cadrées par :
- Chemin URI (
/wp-json/vs « tout WordPress ») - Méthode HTTP (autoriser
GETmais examinerPOST) - Contexte d’authentification (cookie admin présent, ou requêtes depuis un VPN, ou depuis un IdP connu)
- Nom du paramètre (exclure le champ
content, pas tout le body) - ID de règle (désactiver une règle à un endroit précis, pas un ensemble géré entier)
3) Préférez « corriger le déclencheur » plutôt que « désactiver le détecteur »
Si un plugin envoie un paramètre qui ressemble à une injection SQL parce qu’il contient les mots select et union en clair, la bonne correction peut être : encoder correctement, renommer des champs, réduire la réflexion, ou assainir le contenu en amont. Le tuning WAF ne remplace pas l’hygiène applicative.
4) Faites de la place pour des contrôles ennuyeux
WordPress gagne énormément d’atténuations simples et fiables : limitation de débit sur la connexion, filtrage des bots, MFA, comptes administrateurs au moindre privilège, blocage des endpoints inutilisés, et mise à jour des plugins. Ajuster le WAF est moins effrayant quand vous avez des contrôles compensatoires.
Blague n°1 : Désactiver le WAF pour régler WordPress, c’est comme enlever les détecteurs de fumée parce que vous n’aimez pas le bip. Ça marche jusqu’à ce que ça ne marche plus.
Tâches pratiques : commandes, sorties et décisions
Ci‑dessous des tâches pratiques que vous pouvez exécuter sur une origine Linux typique avec Nginx/Apache et ModSecurity, plus quelques vérifications côté CDN/WAF. Chaque tâche inclut : commande, ce que signifie la sortie, et quelle décision prendre.
Tâche 1 : Reproduire le blocage avec une requête connue et capturer les en‑têtes
cr0x@server:~$ curl -kisS https://example.com/wp-login.php | sed -n '1,20p'
HTTP/2 403
date: Fri, 27 Dec 2025 10:14:22 GMT
content-type: text/html; charset=UTF-8
server: cloudflare
cf-ray: 86a1c2d3e4f56789-FRA
Ce que cela signifie : HTTP 403 avec un en‑tête Cloudflare suggère un blocage au niveau edge WAF, pas à l’origine. La valeur cf-ray est votre identifiant de traçage.
Décision : Récupérez l’événement WAF en utilisant l’ID de requête (ou horodatage+IP) avant de toucher aux règles d’origine.
Tâche 2 : Vérifier directement la réponse de l’origine (contourner le CDN) pour prouver où se situe le blocage
cr0x@server:~$ curl -kisS --resolve example.com:443:203.0.113.10 https://example.com/wp-login.php | sed -n '1,20p'
HTTP/2 200
date: Fri, 27 Dec 2025 10:15:01 GMT
content-type: text/html; charset=UTF-8
server: nginx
Ce que cela signifie : L’origine renvoie 200, l’edge renvoie 403. Le WAF/CDN bloque, pas WordPress.
Décision : Ajustez les règles du WAF edge. Ne perdez pas de temps dans les logs PHP pour l’instant.
Tâche 3 : Identifier quel endpoint est bloqué (admin, REST, AJAX, téléversements)
cr0x@server:~$ curl -kisS https://example.com/wp-json/wp/v2/users/me | sed -n '1,15p'
HTTP/2 403
date: Fri, 27 Dec 2025 10:16:11 GMT
content-type: application/json
server: cloudflare
Ce que cela signifie : L’API REST est bloquée. Cela peut casser Gutenberg, les médias et les interfaces de plugins.
Décision : Cherchez des règles gérées ciblant les corps JSON, les en‑têtes d’auth, ou les signatures génériques « attaque API » ; ajustez par /wp-json/ et non globalement.
Tâche 4 : Si ModSecurity est sur l’origine, confirmer qu’il est activé et en quel mode
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -E 'modsecurity|SecRuleEngine' -n
121:modsecurity on;
122:modsecurity_rules_file /etc/nginx/modsec/main.conf;
Ce que cela signifie : Nginx est compilé avec le connecteur ModSecurity et il est activé.
Décision : Étape suivante : vérifier si ModSecurity est en DetectionOnly ou On et inspecter les logs d’audit.
Tâche 5 : Vérifier le réglage SecRuleEngine
cr0x@server:~$ sudo grep -R --line-number 'SecRuleEngine' /etc/nginx/modsec
/etc/nginx/modsec/main.conf:12:SecRuleEngine On
Ce que cela signifie : Les requêtes peuvent être activement bloquées sur l’origine.
Décision : Si vous êtes en plein incident, envisagez un passage temporaire à DetectionOnly pour un emplacement spécifique pendant l’analyse (pas un arrêt global).
Tâche 6 : Localiser le log d’audit ModSecurity et confirmer qu’il reçoit des événements
cr0x@server:~$ sudo grep -R --line-number 'SecAuditLog' /etc/nginx/modsec
/etc/nginx/modsec/main.conf:45:SecAuditLog /var/log/modsec_audit.log
cr0x@server:~$ sudo tail -n 3 /var/log/modsec_audit.log
--c9a1b7d8-H--
Message: Access denied with code 403 (phase 2). Matched "Operator `Rx' with parameter `(?i:(?:union(?:.*)select))'" against variable `ARGS:query' (Value: `union select`) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"] [id "942100"] [severity "CRITICAL"] [tag "application-multi"] [hostname "example.com"] [uri "/wp-admin/admin-ajax.php"] [unique_id "1735294592"]
Ce que cela signifie : La règle 942100 (catégorie SQLi) s’est déclenchée sur ARGS:query dans admin-ajax.php. C’est suffisamment précis pour ajuster de manière ciblée.
Décision : Ne désactivez pas toutes les règles SQLi. Créez une exclusion ciblée pour le paramètre query sur cet endpoint si vous validez que c’est sûr et attendu.
Tâche 7 : Corréler la requête dans les logs d’accès (méthode, taille, user agent, IP client)
cr0x@server:~$ sudo awk '$7 ~ /admin-ajax\.php/ {print $1,$4,$6,$7,$9,$10,$12}' /var/log/nginx/access.log | tail -n 5
198.51.100.23 [27/Dec/2025:10:16:31 +0000] "POST /wp-admin/admin-ajax.php 403 721 "Mozilla/5.0"
198.51.100.23 [27/Dec/2025:10:16:34 +0000] "POST /wp-admin/admin-ajax.php 403 721 "Mozilla/5.0"
Ce que cela signifie : 403 répétés pour POST vers admin-ajax. L’IP cliente est stable ; on dirait un vrai navigateur.
Décision : Si le même blocage touche de nombreux utilisateurs réels sur plusieurs IPs, c’est probablement un faux positif. Si c’est une IP qui martèle, il peut s’agir d’un attaquant — ne touchez pas au tuning, bloquez.
Tâche 8 : Extraire les valeurs des paramètres correspondants depuis le log d’audit pour voir ce qui déclenche
cr0x@server:~$ sudo grep -n 'Matched "Operator' -n /var/log/modsec_audit.log | tail -n 2
4128:Message: Access denied with code 403 (phase 2). Matched "Operator `Rx' with parameter `(?i:(?:union(?:.*)select))'" against variable `ARGS:query' (Value: `union select`) [id "942100"] [uri "/wp-admin/admin-ajax.php"] [unique_id "1735294592"]
Ce que cela signifie : La valeur littérale union select est présente. Cela peut être une charge malveillante, ou une interface de recherche/filtre qui autorise une syntaxe SQL (mauvaise idée, mais ça arrive).
Décision : Validez la fonctionnalité qui génère cela. Si c’est une syntaxe de recherche légitime, vous avez besoin de contrôles compensatoires (auth-only, vérifications de nonce, limitation de débit) avant d’exclure.
Tâche 9 : Confirmer le contexte nonce/auth WordPress pour les appels admin-ajax
cr0x@server:~$ curl -kisS -X POST https://example.com/wp-admin/admin-ajax.php \
-d 'action=test_action&_ajax_nonce=deadbeef&query=union%20select' | sed -n '1,25p'
HTTP/2 403
date: Fri, 27 Dec 2025 10:18:12 GMT
content-type: text/html; charset=UTF-8
Ce que cela signifie : Toujours bloqué par le WAF ; vous n’atteignez même pas la validation de nonce de WordPress.
Décision : Si vous prévoyez une exception pour admin-ajax, restreignez-la aux sessions administrateur authentifiées (lorsque possible) ou à des actions/paramètres spécifiques plutôt qu’un contournement générique d’admin-ajax.
Tâche 10 : Pour Apache + ModSecurity, confirmer quel jeu de règles est chargé (CRS) et les indices de version
cr0x@server:~$ sudo apachectl -M 2>/dev/null | grep -i security
security2_module (shared)
cr0x@server:~$ sudo grep -R --line-number 'owasp-crs' /etc/apache2 | head
/etc/apache2/mods-enabled/security2.conf:15:IncludeOptional /usr/share/modsecurity-crs/owasp-crs.load
Ce que cela signifie : Vous utilisez OWASP CRS. Les IDs de règle comme 942100 correspondent au CRS SQLi.
Décision : Utilisez les mécanismes d’exclusion pris en charge par CRS ; évitez d’éditer directement les fichiers de règles du fournisseur (cela complique les mises à jour).
Tâche 11 : Créer une exclusion ModSecurity étroite (exemple) et valider la syntaxe
cr0x@server:~$ sudo tee /etc/nginx/modsec/wordpress-exclusions.conf >/dev/null <<'EOF'
# Narrow exclusion: only for admin-ajax.php, only remove one parameter from one rule
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" "id:1001001,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:query"
EOF
cr0x@server:~$ sudo grep -R --line-number 'wordpress-exclusions' /etc/nginx/modsec/main.conf
72:Include /etc/nginx/modsec/wordpress-exclusions.conf
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 : Vous avez retiré le paramètre query de l’inspection par la règle 942100 uniquement sur cette URI. Tout le reste est toujours inspecté.
Décision : Rechargez et testez. Si des attaques se déplacent vers d’autres paramètres, vous verrez de nouveaux hits de règle. C’est normal ; vous itérez avec des preuves.
Tâche 12 : Recharger et retester la requête exacte en échec
cr0x@server:~$ sudo systemctl reload nginx
cr0x@server:~$ curl -kisS -X POST https://example.com/wp-admin/admin-ajax.php \
-d 'action=test_action&_ajax_nonce=deadbeef&query=union%20select' | sed -n '1,25p'
HTTP/2 200
date: Fri, 27 Dec 2025 10:19:44 GMT
content-type: text/html; charset=UTF-8
Ce que cela signifie : Le WAF ne bloque plus cette requête. Maintenant vous devez vous assurer que WordPress rejette toujours les nonces invalides et le comportement non authentifié comme prévu.
Décision : Confirmez que la couche applicative valide toujours l’auth/nonce ; si ce n’est pas le cas, votre « correctif » WAF vient d’ouvrir une voie d’exploitation.
Tâche 13 : Vérifier que WordPress bloque toujours les actions dangereuses non authentifiées
cr0x@server:~$ curl -kisS -X POST https://example.com/wp-admin/admin-ajax.php \
-d 'action=delete_user&user_id=1' | sed -n '1,30p'
HTTP/2 200
date: Fri, 27 Dec 2025 10:20:12 GMT
content-type: text/html; charset=UTF-8
0
Ce que cela signifie : Beaucoup d’endpoints AJAX WordPress renvoient 0 pour non autorisé. C’est plutôt bon ; cela signifie que vous atteignez WordPress et qu’il refuse l’action.
Décision : Si vous voyez une réponse réussie pour des actions privilégiées sans auth, stoppez. Restaurez l’exclusion et corrigez d’abord l’application pour imposer auth/nonce.
Tâche 14 : Vérifier si de gros cookies déclenchent les limites d’en‑têtes (un déclencheur sournois)
cr0x@server:~$ curl -kisS https://example.com/ | awk 'tolower($0) ~ /^set-cookie:/ {print length($0), $0}' | head
148 Set-Cookie: wordpress_test_cookie=WP%20Cookie%20check; path=/
312 Set-Cookie: wp-settings-1=libraryContent%3Dbrowse; path=/; expires=Sat, 26 Dec 2026 10:20:55 GMT
Ce que cela signifie : Pas énorme, mais si vous voyez des lignes de cookie très longues (milliers d’octets), certains WAFs/proxies rejettent les requêtes quand l’en‑tête Cookie gonfle.
Décision : Si les cookies sont massifs, réduisez la surcharge de cookies (nettoyage de plugins, limiter le scope des cookies, éviter d’y stocker de l’état) plutôt que d’adapter le WAF à accepter des en‑têtes absurdes.
Tâche 15 : Détecter les 429 (limitation de débit) vs 403 (signature) dans les logs
cr0x@server:~$ sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
981 200
114 403
62 404
27 429
Ce que cela signifie : Certaines requêtes sont limitées (429). C’est une voie d’ajustement différente d’un blocage par signature.
Décision : Si des éditeurs reçoivent des 429 pendant un usage admin normal, ajustez les limites par endpoint et méthode. N’affaiblissez pas les règles SQLi/XSS pour résoudre un problème de limitation de débit.
Tâche 16 : Confirmer la préservation de l’IP client réelle (le tuning WAF échoue si les IP sont fausses)
cr0x@server:~$ sudo grep -R --line-number 'real_ip_header|set_real_ip_from' /etc/nginx | head -n 20
/etc/nginx/conf.d/realip.conf:1:real_ip_header CF-Connecting-IP;
/etc/nginx/conf.d/realip.conf:2:set_real_ip_from 173.245.48.0/20;
/etc/nginx/conf.d/realip.conf:3:set_real_ip_from 103.21.244.0/22;
Ce que cela signifie : Nginx fait confiance à un en‑tête et à des plages d’IP de proxy spécifiques. Sans cela, les limitations de débit et la corrélation WAF peuvent être absurdes.
Décision : Si vous voyez toutes les requêtes identifiées par l’IP du proxy, corrigez la gestion des IP réelles avant d’ajuster des règles comportementales.
Schémas d’ajustement qui fonctionnent (et leur coût)
Schéma A : Exclure un paramètre d’une seule règle sur un seul endpoint
C’est la référence pour les faux positifs. Vous gardez la règle active partout ailleurs. Vous gardez aussi les autres règles actives sur cet endpoint. Vous conservez l’audit logging.
Idéal pour : champs de contenu Gutenberg/éditeur, champs de recherche, clés de payload JSON qui ressemblent à du code.
Coût : Vous devez maintenir une petite cartographie des « paramètres bruyants connus ». Ce n’est pas un vrai coût ; c’est la gestion de votre système.
Schéma B : Politiques séparées pour le trafic admin vs public
Les endpoints d’administration se comportent différemment. Ils devraient aussi être protégés différemment. Dans de nombreuses organisations, la meilleure mesure est de placer l’administration derrière des contrôles supplémentaires :
- Exiger MFA / SSO
- Restreindre par VPN ou IPs egress corporatives (si faisable)
- Limiter fortement les connexions sur la page de login ; assouplir sur les bodies POST de l’éditeur où du HTML légitime existe
Idéal pour : sites avec de nombreux éditeurs et un fort usage admin.
Coût : Plus d’objets de politique et de tests. Toujours moins coûteux que le temps d’arrêt et la mise en liste blanche panique.
Schéma C : Passer des règles gérées de « block » à « count » temporairement — de manière chirurgicale
Parfois vous ne savez pas si c’est un faux positif tant que vous n’avez pas collecté plus d’événements. Placez un groupe de règles gérées en mode count/detect-only pour un endpoint pendant une courte fenêtre.
Idéal pour : incidents à fort impact business où la preuve issue de la correspondance de règle est ambiguë.
Coût : Risque à court terme. Vous devez compenser par la limitation de débit et la surveillance durant la fenêtre.
Schéma D : Bloquer ce que vous n’utilisez pas (cela réduit le risque et le bruit WAF)
Si vous n’avez pas besoin de /xmlrpc.php, bloquez‑le à l’edge ou à l’origine. Idem pour les routes REST inutilisées ou les endpoints legacy. Moins de surface réduit les payloads étranges et les collisions de règles.
Blague n°2 : XML‑RPC, c’est comme cette vieille imprimante dans le coin — personne n’admet l’utiliser, mais elle cause encore des incidents.
Trois mini-histoires d’entreprise tirées de systèmes réels
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne a migré son site marketing vers WordPress derrière un WAF géré. La checklist de migration disait « activer les règles gérées, mettre en block, déployer ». L’hypothèse était que les règles gérées sont « sûres par défaut ». La plupart du temps oui. Jusqu’à ce qu’elles ne le soient pas.
Le premier lundi, les éditeurs ont commencé à utiliser un nouveau plugin de page builder qui sauvegardait des layouts en gros blobs JSON via /wp-json/. Le WAF a commencé à bloquer les requêtes comme « attaque de protocole : JSON invalide » et « XSS dans le body » parce que le builder stockait des fragments HTML et des styles inline dans des chaînes JSON.
L’équipe ingénierie a supposé que c’était un bug WordPress. Le marketing a supposé que l’ingénierie « bloquait la créativité ». La sécurité a supposé que le marketing téléversait des malwares. Chacun avait tort à sa façon.
La solution n’a pas été de désactiver le WAF. Ils ont extrait les logs d’événements WAF, trouvé deux règles bruyantes, et exclu des clés JSON spécifiques sur la route /wp-json/ tout en gardant les règles XSS actives ailleurs. Ils ont aussi ajouté une taille maximale et normalisé l’encodage à la frontière applicative. L’incident s’est terminé, et la dispute aussi.
Mini‑histoire 2 : L’optimisation qui a mal tourné
Une organisation plus grande a décidé « d’optimiser les performances » en mettant davantage en cache au CDN. Ils ont ajouté une règle : mettre en cache tout ce qui est sous /wp-json/ pendant 10 minutes. Ils ont aussi maintenu un WAF strict sur les APIs parce que « les APIs sont dangereuses ». Deux décisions raisonnables combinées ont créé le chaos.
Les éditeurs ont vu des échecs d’autorisation aléatoires et des 403 intermittents. Certaines réponses 200 authentifiées ont été mises en cache et servies à des clients non authentifiés, ce qui a déclenché des vérifications bot et des incohérences de politique. Parallèlement, les nonces WordPress expiraient de façon imprévisible parce que l’UI mélangeait réponses mises en cache et réponses live.
L’équipe ingénierie a d’abord ajusté le WAF, parce que 403 voulait dire « problème WAF ». Cet ajustement a empiré la situation : désormais le mauvais contenu mis en cache circulait plus librement. La vraie cause racine était la mise en cache d’un endpoint qui ne devait jamais être mis en cache sans règles de vary strictes et segmentation d’auth.
Ils ont annulé le cache pour les routes REST authentifiées, conservé le cache uniquement pour les routes GET publiques explicites, et resserré les règles WAF autour de la connexion et de XML‑RPC. Les performances sont redevenues bonnes, les erreurs ont chuté, et tout le monde a retenu la même leçon : « plus rapide » n’est pas une fonctionnalité si c’est faux.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise réglementée faisait tourner WordPress pour un portail client. Leur équipe sécurité exigeait que chaque changement WAF passe par un petit pipeline : log‑only pendant 24 heures, revue des correspondances principales, puis mise en application. Ça paraissait bureaucratique. C’était aussi la raison pour laquelle ils dormaient tranquille.
Un jour, une mise à jour de plugin a changé un formulaire admin pour soumettre du contenu avec un paramètre nommé template contenant des fragments HTML et des shortcodes. Le WAF a commencé à le détecter comme « injection PHP » sur les pages admin. Parce que le processus de changement WAF incluait des tableaux de bord de référence et un mode staging, ils ont remarqué le pic avant de mettre la règle en block en production.
Ils ont implémenté une exclusion cadrée aux URIs admin authentifiées et au paramètre spécifique, puis ajouté un test unitaire qui poste la charge exacte via le WAF dans le CI. Ennuyeux. Prévisible. Efficace.
Quand un auditeur a demandé plus tard « comment empêchez‑vous la désactivation d’urgence des contrôles de sécurité », ils ont eu une réponse qui n’était pas une promesse. C’était une preuve.
Erreurs courantes : symptôme → cause racine → correctif
1) Les éditeurs ne peuvent pas sauvegarder les articles ; Gutenberg affiche « Updating failed »
Symptôme : 403/406 intermittents sur les requêtes POST /wp-json/.
Cause racine : règle WAF correspondante sur le body JSON (fragments HTML, scripts inline, ou CSS) ou règles de parsing JSON strictes.
Correctif : Exclure des clés/paramètres JSON spécifiques pour des IDs de règle spécifiques sur /wp-json/ POST, garder la journalisation. Confirmer que l’auth et la validation des nonces WordPress restent appliquées.
2) Les téléversements média échouent avec 403 ; seules les gros fichiers échouent
Symptôme : petits fichiers ok, gros fichiers échouent ; parfois 413 aussi.
Cause racine : limites de taille du body au niveau WAF/CDN/origine ; règles de parsing multipart ; timeouts d’intégration antivirus.
Correctif : Aligner les limites entre les couches (CDN, WAF, Nginx/Apache, PHP). Préférer augmenter les limites plutôt que de contourner l’inspection pour les uploads ; si contournement nécessaire, ajouter des contrôles stricts de type de fichier et un scanning anti‑malware.
3) wp-admin fonctionne via VPN mais pas depuis des réseaux domestiques
Symptôme : 403 seulement pour certains FAI/régions ; l’admin apparaît comme « bot ».
Cause racine : règles de protection bot ou de réputation trop agressives ; challenges/cookies manquants à cause de paramètres de confidentialité ; ou origine qui interprète mal l’IP client et déclenche des limitations de débit.
Correctif : Corriger la gestion des IP réelles ; ajuster les règles bot spécifiquement pour /wp-login.php et /wp-admin/ ; envisager des challenges « step‑up » uniquement lors de la connexion.
4) Les requêtes GET de l’API REST sont bloquées mais les pages normales fonctionnent
Symptôme : site public chargé, mais intégrations en panne.
Cause racine : groupe de règles géré « protection API » bloque des motifs courants dans les paramètres de requête REST.
Correctif : Mettre en liste blanche des routes REST spécifiques et des méthodes ; exclure seulement les noms de paramètres fautifs pour des IDs de règle spécifiques ; ajouter limitation de débit et contrôles d’auth pour les endpoints sensibles.
5) Tout ce qui est sous /wp-admin/ est mis en liste blanche « temporairement », puis jamais révoqué
Symptôme : l’équipe sécurité se plaint plus tard ; vous trouvez une règle de contournement vieille de plusieurs mois.
Cause racine : correctif d’incident sans plan de rollback ; absence d’expiration sur les politiques d’urgence.
Correctif : Ajouter une expiration explicite pour les exceptions d’urgence, plus des alertes pour « règles de contournement présentes ». Intégrez‑les au change management, pas à la mémoire humaine.
6) Le WAF bloque du HTML légitime dans le contenu de l’article
Symptôme : sauvegarde d’un article déclenche les règles XSS.
Cause racine : le WAF voit du HTML et pense « attaque ». Le contenu WordPress est littéralement du HTML. Incroyable, je sais.
Correctif : Exclure les champs de contenu pour les endpoints de l’éditeur uniquement ; assurer que la fonction KSES/assainissement HTML de WordPress est activée et que vous ne donnez pas unfiltered_html à tout le monde.
7) Les attaques par force brute sur les connexions réussissent encore après tuning
Symptôme : le tuning a réduit les blocages mais augmenté le succès des attaques.
Cause racine : vous avez affaibli les protections liées à l’auth (limitation de débit, vérifications bot) en chassant des faux positifs ailleurs.
Correctif : Séparer les politiques : strictes sur /wp-login.php, /xmlrpc.php et l’administration ; exceptions mesurées sur les payloads éditeur/REST.
Listes de contrôle / plan étape par étape
Plan étape par étape pour ajuster sans créer une faille
- Capturer une requête en échec : horodatage, IP cliente, URI, méthode, ID de requête (en‑tête de traçage edge si présent).
- Prouver la couche qui bloque : edge vs origine en utilisant
curl --resolveou un accès direct à l’origine depuis un segment réseau de confiance. - Récupérer l’événement WAF : ID/groupe de règle, variable matchée, extrait de payload, action prise (block/challenge/log).
- Classer l’événement :
- Ressemble à une attaque depuis des IPs aléatoires ? Traiter comme vrai positif.
- Se produit chez des éditeurs authentifiés sur de nombreuses IPs ? Probablement faux positif.
- Principalement 429 ? C’est la limitation de débit, pas des signatures.
- Choisir le plus petit levier :
- Exclure un seul paramètre d’une règle ID sur une URI, ou
- Restreindre l’exception à méthode + URI + contexte authentifié, ou
- Mettre temporairement en count/detect‑only un groupe pour un endpoint pendant la collecte de preuves.
- Ajouter des contrôles compensatoires quand vous assouplissez l’inspection :
- Assurer que les vérifications nonce/auth de WordPress sont appliquées
- Limiter par IP/user admin-ajax et login
- Bloquer les endpoints inutilisés comme XML‑RPC si non requis
- Tester l’échec exact et quelques cas d’abus : répéter la requête fautive ; essayer aussi des payloads évidents et vérifier qu’ils sont toujours bloqués ailleurs.
- Surveiller les dérives : nouveaux hits de règles en tête, augmentation des 5xx, hausse des échecs d’auth, ou chute soudaine des blocs (signe d’un contournement excessif).
- Documenter l’exception : endpoint, ID de règle, raison, propriétaire, date d’expiration, et plan de rollback.
Checklist opérationnelle pour être prêt en production
- Les logs WAF sont accessibles, consultables et conservés assez longtemps pour déboguer des incidents.
- Les endpoints admin ont des contrôles plus stricts que les endpoints publics (séparation de politique).
- Les règles de contournement d’urgence ont une expiration et des alertes.
- Une limitation de débit existe pour
/wp-login.phpet/xmlrpc.php. - L’IP client réelle est correctement propagée vers les logs d’origine et les outils de sécurité.
- Le staging peut reproduire le comportement WAF (ou vous disposez d’une fenêtre « count-only » pour collecter des preuves en sécurité).
FAQ
1) Dois‑je simplement mettre /wp-admin/ en liste blanche dans le WAF ?
Non. C’est là que résident les actions les plus importantes. Si vous devez réduire les frictions, limitez les exceptions à des endpoints et paramètres admin spécifiques, et gardez des protections strictes sur la connexion.
2) Est‑ce sûr de désactiver un ID de règle OWASP CRS ?
Parfois, mais faites‑le comme une chirurgie : désactivez ou retirez la cible la plus petite possible, et seulement pour l’endpoint où elle est bruyante. Ne faites pas de « ruleRemoveById » global à moins d’aimer expliquer après des incidents.
3) Pourquoi admin-ajax.php déclenche‑t‑il autant de règles WAF ?
Parce que c’est un endpoint POST très fréquent avec beaucoup de paramètres et d’actions définies par les plugins. Les attaquants l’adorent, les plugins l’abusent, et les WAF remarquent les deux.
4) Dois‑je bloquer xmlrpc.php ?
Si vous n’en avez pas explicitement besoin, oui — bloquez‑le à l’edge et à l’origine. Si vous en avez besoin, limitez fortement son débit et envisagez d’autoriser seulement des clients connus.
5) Comment savoir si c’est un faux positif ou une vraie attaque ?
Regardez le contexte : s’agit‑il de trafic éditeur authentifié, d’agents utilisateurs cohérents, et reproductible depuis des workflows normaux ? Ou s’agit‑il d’un spray d’IP aléatoires avec des payloads évidents ? Utilisez les logs d’audit : la variable matchée + l’extrait de payload clarifient généralement.
6) Le caching peut‑il provoquer des blocs WAF ?
Indirectement, oui. Un mauvais cache de réponses REST authentifiées peut créer des incohérences et des retries répétés qui déclenchent des règles bot/limitation. Corrigez la stratégie de cache avant d’assouplir les règles de sécurité.
7) Comment ajuster les règles WAF sans perdre en visibilité ?
Gardez la journalisation activée même lorsque vous excluez des cibles. Utilisez des modes count/detect‑only pour de courtes fenêtres de collecte de preuves. Construisez des tableaux de bord pour les IDs de règle principaux et les URIs affectées afin que les exceptions ne deviennent pas invisibles pour toujours.
8) Quelle est la manière la plus sûre de gérer le HTML d’éditeur qui déclenche des règles XSS ?
Excluez le champ de contenu spécifique sur l’endpoint de l’éditeur à partir de règles XSS précises, et assurez‑vous que la sanitisation WordPress et les capacités utilisateur sont configurées correctement.
9) Que faire si le fournisseur WAF ne me montre pas la charge correspondante ?
Alors vous avez besoin de meilleurs logs à un niveau que vous contrôlez (logs d’audit WAF d’origine, logs debug d’un reverse proxy avec prudence, ou un environnement de staging). Ajuster sans données de correspondance, c’est deviner avec du papier administratif.
Prochaines étapes pratiques
Faites trois choses aujourd’hui, dans cet ordre :
- Obtenez la traçabilité : assurez‑vous que chaque blocage vous donne un ID de requête et un ID de règle que vous pouvez rechercher. Si ce n’est pas le cas, corrigez la journalisation avant que le prochain incident ne vous force à « contourner temporairement » et à vous retrouver coincé.
- Scindez la politique par endpoint : traitez
/wp-login.php,/xmlrpc.php,/wp-admin/,/wp-json/et/admin-ajax.phpcomme des produits différents avec des profils de risque différents. - Implémentez des exclusions étroites avec expiration : excluez des paramètres de règles spécifiques, pas des groupes entiers. Ajoutez un propriétaire et une date d’expiration, et rendez le rollback ennuyeux.
Votre WAF doit être un scalpel, pas un bandeau sur les yeux. S’il bloque WordPress, vous n’avez pas besoin de moins de sécurité — vous avez besoin d’un meilleur ajustement.