Merci @ursrig pour le feedback et l’idee du WAF
Sur le associatif sous SPIP 4.4.15 en mutualisé, le plugin WAF y tourne depuis quelques jours.
Ca me semble tres bien 
Je ne suis pas développeur, j’ai simplement donné à Claude un extrait de mon tableau de bord et une copie locale du dépôt git et le htaccess, en lui demandant ce qu’il en tirait sous forme de rapport pour le développeur, je vous partage la response, si ca peut être utile
EN tout cas, merci pour le plugin, c’est tres pour les asso avec peu de moyen masi avoir un outil simple de protection sans devoir €€€…
dans la config, ce qui est actif :
- FireHOL niveau 1 (niv2 pas actif)
- En-tête IP client : REMOTE_ADDR
- Enregistrer les échecs de connexion comme strikes WAF
- Désactiver les règles WAF pour les utilisateurs connectés (rédacteurs, administrateurs)
RAPPORT — Plugin SPIP WAF v1.2.18
Auteur : Claude (Anthropic), à la demande du webmestre du site
Date : 28 août 2026
Objet : couverture des règles de détection et points d’extension
1. Périmètre et méthode
Sources analysées :
- Extraits du tableau de bord WAF d’un site en production, fenêtre de 7 jours
- Lecture statique du code source, copie locale du dépôt git — version
1.2.18, paquet.xml généré le 2026-04-03, compatibilité [4.4.13;4.*]
- Le
.htaccess du site, afin de vérifier qu’aucun filtrage serveur ne masquait les constats
Limites, à lire avant les constats :
- Analyse statique uniquement. Aucun test dynamique, aucune requête d’attaque rejouée. Les conclusions sur ce qui « passerait » sont déduites de la lecture des règles, pas observées.
- Aucune modification appliquée sur le site audité.
- L’arborescence complète de la racine web n’a pas été inspectée (copie locale partielle).
- Il ne s’agit pas d’un test d’intrusion.
2. Télémétrie observée
Sur 7 jours : 273 événements, dont 268 blocages, sur 22 IP uniques.
- 262 blocages attribués aux règles propres du plugin (16 IP)
- 6 blocages via les listes publiques (6 IP) — FireHOL niveau 1 à 0 %
- Une IP nettement dominante,
34.125.205.23 (plage Google Cloud), en bannissement long terme
Comportements identifiés :
- SSRF vers les métadonnées cloud :
/fetch?uri=http://169.254.169.254/latest/meta-data/iam/security-credentials/, avec variantes url=, target=, dest=, path=, proxy?url=
- Balayage de fichiers sensibles :
.env, aws/credentials, secrets.yaml, service-account.json, ssl/server.key, .git/config
- RCE de frameworks tiers : ThinkPHP (
?s=/Index/\think\app/…), Laravel Ignition (/_ignition/health-check)
- Usurpation d’User-Agent : la même IP alterne GrokBot, Perplexity-User et TelegramBot avec des versions de Chrome légèrement différentes
3. Constats
C1 — Absence de règle SSRF / métadonnées cloud · Impact : moyen
Ni waf_malicious_payload_patterns() ni waf_cms_probe_patterns() ne contiennent de motif visant les services de métadonnées d’instance. L’unique occurrence de 169.254.0.0/16 est dans waf_ti_local_cidrs() (waf_fonctions.php:1325), qui protège le téléchargeur de listes du plugin lui-même — usage distinct, sans effet sur les requêtes entrantes.
Conséquence déduite : sur le site observé, ces requêtes n’ont été bloquées que parce que l’IP était déjà bannie pour d’autres motifs. Depuis une IP sans historique, elles ne devraient rencontrer aucune règle. Non vérifié par un test réel.
Si une règle devait être ajoutée, il paraît important de rester sur des chaînes très spécifiques — 169.254.169.254, metadata.google.internal, /latest/meta-data/, /computeMetadata/v1/ — et d’éviter le mot metadata seul, qui produirait des faux positifs dès qu’un visiteur l’emploie dans une recherche ou une contribution.
C2 — Sonde .env ancrée à la racine uniquement · Impact : moyen
Le motif est '/^' . $p . '\/\.env$/i', ancré en début de chemin et terminé par $. Les variantes observées y échappent : /api/.env, /backend/.env, /.env.bak, /.env.local. Ne sont pas couverts non plus /aws/credentials, /secrets.yaml, /service-account.json, /ssl/server.key, /.git/config.
Voir toutefois C6 : ce constat appelle sans doute une réponse au niveau serveur plutôt qu’au niveau PHP.
C3 — RCE de frameworks tiers absentes de la liste · Impact : faible à moyen
/_ignition/health-check (Laravel Ignition, CVE-2021-3129) et la chaîne ThinkPHP figurent parmi les sondes les plus fréquentes du trafic observé et ne correspondent à aucun motif existant. Ces chemins n’existant jamais sur une installation SPIP, le risque de faux positif d’une règle les visant est nul.
C4 — Le pipeline waf_custom_rules n’est pas déclaré · Impact : ergonomie développeur
Le pipeline est bien appelé (waf_fonctions.php:354) et documenté par un exemple en commentaire juste au-dessus. En revanche il n’apparaît pas dans paquet.xml, contrairement aux autres pipelines du plugin. Il n’est donc découvrable qu’en lisant le source.
C’est dommage, car c’est précisément le mécanisme qui permet à un webmestre d’ajouter ses propres motifs sans forker le plugin ni perdre ses règles à chaque mise à jour. Le déclarer et le mentionner sur la page contrib augmenterait nettement son usage.
Détail annexe : l’exemple en commentaire procède par affectation directe, ce qui écraserait un autre abonné éventuel. Une concaténation serait plus robuste :
$GLOBALS['spip_pipeline']['waf_custom_rules'] = ($GLOBALS['spip_pipeline']['waf_custom_rules'] ?? '') . '|ma_fonction';
C5 — Le tableau de bord surestime la couverture des règles · Impact : lisibilité
Le contrôle de bannissement s’exécute en waf_fonctions.php:300, donc avant les règles de contenu (ligne 356 et suivantes). Une fois une IP bannie, l’intégralité de son trafic est journalisée sous BANNED_IP, y compris des requêtes qu’aucune règle de contenu ne détecterait.
Le tableau donne alors l’impression d’une couverture plus large qu’elle ne l’est — c’est précisément ce qui a motivé la lecture du code dans le cas présent. Afficher, même en second plan, la règle qui aurait matché sur une requête déjà bloquée par bannissement donnerait une image fidèle de la couverture réelle et aiderait à repérer les angles morts.
C6 — Le WAF ne voit pas les requêtes servies directement par Apache · Impact : à documenter
Le .htaccess standard de SPIP court-circuite la réécriture lorsque le fichier demandé existe :
apache
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule "." - [skip=100]
Dans ce cas Apache sert le fichier directement, SPIP n’est pas chargé, et le WAF ne s’exécute pas.
C’est inhérent à tout WAF applicatif en PHP et non un défaut d’implémentation, mais la conséquence mériterait d’être explicitée dans la documentation : le plugin ne protège pas les fichiers présents sur le disque, en particulier un script déposé après une compromission. Un webmestre peut raisonnablement croire l’inverse.
Cela invite aussi à réviser l’approche du constat C2 : les sondes de fichiers sensibles sont bloquées plus efficacement, et à moindre coût d’exécution, au niveau serveur qu’au niveau PHP. Fournir dans la documentation du plugin un extrait .htaccess recommandé — .env, .git/, *.sql, *.bak, fichiers de clés — serait un complément naturel.
À noter au passage : le .htaccess livré par SPIP bloque .svn/ mais pas .git/, héritage de l’époque Subversion, alors que /.git/config est aujourd’hui une des sondes les plus courantes.
4. Points solides relevés
L’architecture est saine et plusieurs choix méritent d’être soulignés.
- Ordre des contrôles pertinent : allowlist, denylist manuelle, listes publiques, bannissement, puis règles de contenu. Les tests les moins coûteux d’abord.
- Bypass des rédacteurs connectés (
_WAF_BYPASS_LOGGED_IN, ligne 320) : évite qu’un article contenant system(reboot) fasse bannir son auteur, tout en maintenant les protections IP. C’est un piège classique des WAF, correctement traité ici.
- Motifs de contenu prudents : les règles SQLi exigent des mots-clés structurels, la traversée de répertoire demande au moins deux
../ consécutifs, base64_decode n’est signalé que suivi d’une parenthèse. Le souci d’éviter les faux positifs est visible jusque dans les commentaires.
- Journalisation différée (
waf_flush_events, 300 s) et troncature du contexte à _WAF_EVENT_CONTEXT : protège la base d’un remplissage sous requêtes volumineuses.
- Points d’extension nombreux :
waf_bypass (l.207), waf_custom_rules (l.354), waf_on_ban (l.727), waf_handle_violation (l.999), waf_stats_extra.
5. Pistes, par rapport bénéfice / effort
- Déclarer
waf_custom_rules dans paquet.xml et le documenter sur la page contrib (C4). Effort minimal, permet à chaque site de combler ses propres angles morts sans attendre une version du plugin.
- Documenter un extrait
.htaccess complémentaire plutôt que d’ajouter les motifs de fichiers sensibles aux règles PHP (C6, C2). Plus efficace, quasiment gratuit, et lève au passage un malentendu sur le périmètre du plugin.
- Ajouter un jeu de motifs SSRF (C1), en restant sur des chaînes très spécifiques.
- Ajouter les sondes RCE de frameworks tiers (C3) :
/_ignition/, invokefunction, think\app, /actuator/env.
- Enrichir le tableau de bord de la règle « virtuelle » sur les requêtes bloquées par bannissement (C5).
6. Ce qu’il resterait à vérifier
Points hors de portée d’une analyse statique, à confirmer par le mainteneur :
- Comportement réel d’une requête SSRF émise depuis une IP sans historique de strikes — validerait ou infirmerait C1.
- Efficacité des listes publiques : FireHOL niveau 1 à 0 % sur la fenêtre observée, sans qu’on puisse distinguer un problème de rafraîchissement d’une simple absence de recoupement avec le trafic reçu.
- Détection d’usurpation d’User-Agent (bot déclaré hors des plages IP officielles). Piste évoquée mais non recommandée en l’état : le risque de faux positifs sur des crawlers légitimes est élevé et demanderait une évaluation à part entière.