Repository navigation
WAF FR
English · 🌐 Français
Arenet embarque Coraza v3.7 — un moteur WAF Go-native compatible avec le format de règles ModSecurity — pré-chargé avec OWASP CRS v4.25 (Core Rule Set). Chaque route peut opt-in à la protection WAF à l'un des trois niveaux :
| Mode | Comportement |
|---|---|
off |
Handler WAF pas dans la chain. Zéro overhead. |
detect |
Le WAF évalue chaque requête, émet un waf_event dans /security pour chaque match, mais laisse la requête continuer vers l'upstream. À utiliser pour observation / triage. |
block |
Le WAF évalue, émet l'événement, et rejette la requête avec 403 Forbidden quand le score d'anomalie dépasse le threshold de blocage. |
- Sidebar → Routes
- Clique ta route → Edit
-
WAF mode → choisis
detect(recommandé pour le premier déploiement) - Save
En 5 secondes le WAF est actif. Envoie un probe pour tester :
curl "https://your-route.example.com/?id=1' OR '1'='1"
# En detect mode : la requête atteint ton backend ET un événement SQLi land dans /security
# En block mode : 403 Forbidden, la requête n'atteint pas le backendOuvre /security dans l'UI : tu verras l'événement listé avec catégorie SQLi, l'ID de règle CRS qui a matché (942100 pour libinjection), IP source + GeoIP, l'échantillon de payload qui a matché.
Chaque match émet une ligne dans la table SQLite waf_event avec :
-
ts— timestamp -
route_id— quelle route a triggered -
rule_id— ID de règle CRS (ex.942100,920170) -
category— classification Arenet (SQLi / XSS / RCE / LFI / RFI / PROTOCOL / METHOD / SCANNER / SESSION / DATA_LEAK / ...) — dérivée du range d'ID de règle, voirinternal/waf/category.go -
severity— CRITICAL / ERROR / WARNING / NOTICE -
src_ip— IP distante -
request_path—{method} {uri} -
payload_sample— premiers 256 chars de la data qui a matché -
action—BLOCK(block mode + score ≥ threshold) ouDETECT(detect mode OU block mode + score < threshold) -
status_code—403pour blocked,200pour detect/observed -
matched_var(v2.36+) — le champ qui a déclenché la règle, sous la formeVARIABLE:nom(ex.ARGS:content,REQUEST_HEADERS:referer). Vide pour les événements enregistrés avant la v2.36 et pour les règles qui regardent l'URL elle-même.
La page Journaux et le détail /security/<routeId> listent ces événements. Pour les admins, chaque événement excluable a un bouton Exclure… (voir Exclusion ciblée plus bas).
Les répétitions d'un même trio route + IP source + règle dans les 60 s ne sont enregistrées qu'une fois (les compteurs du tableau de bord comptent quand même chaque requête).
Quand le WAF bloque une requête légitime (un faux positif connu), tu as quatre portes de sortie à appliquer par route, de la plus étroite à la plus large :
À utiliser quand : une règle se déclenche sur un champ légitime précis — le paramètre content d'un éditeur de texte qui déclenche la règle SQLi 942100, un en-tête Referer long qui déclenche une règle XSS…
- Ouvre Journaux (ou la page Sécurité de la route) et repère l'événement.
- Clique sur Exclure…. La fenêtre dit en clair ce qui va se passer : « La règle 942100 n'inspectera plus le paramètre « content ». Elle continue de protéger tous les autres champs de la route. »
-
Seulement sur ce chemin est coché par défaut, avec le chemin de l'événement (ex.
/api/save). Modifie-le si besoin, coche et tout ce qui est en dessous pour toute une arborescence (/apicouvre/apiet/api/…, pas/apix), ou décoche pour couvrir tous les chemins de la route. - Exclure. Caddy recharge ; la même requête passe, alors que la même charge dans un autre champ, ou sur un autre chemin, reste inspectée.
C'est la méthode recommandée par OWASP : la règle reste active, seul ce champ est ignoré (ctl:ruleRemoveTargetById).
À savoir :
- Quand l'événement n'indique pas le champ (événements enregistrés avant la v2.36, ou règle qui inspecte l'URL elle-même), la fenêtre te prévient et exclut la règle sur toute la route (comme l'option (c)).
- Les règles d'évaluation du blocage du CRS (
949xxx,959xxx), d'initialisation (901xxx) et de corrélation (980xxx) n'ont pas de bouton : les exclure désactiverait le blocage lui-même. Exclus plutôt la règle listée à côté (ex.942100). Les règles propres à Arenet (1xxxxx) n'en ont pas non plus. - La section WAF du formulaire de route liste les exclusions ciblées (Exclusions ciblées), une ligne lisible chacune, avec Supprimer et un petit formulaire pour en ajouter une à la main (ID de règle, type de champ, nom du champ, chemin optionnel).
- Réservé aux admins. Chaque ajout est inscrit au journal d'audit comme une modification de route.
À utiliser quand : la route sert du trafic interne de confiance (outil d'admin sur LAN), ou le backend est tellement bruyant que CRS produit 90% de faux positifs.
- Édite la route → section WAF → Disable OWASP CRS on this route ✅
- Un dialogue de confirmation (ADR D4) prévient des implications sécurité
- Save
Le handler WAF reste dans la chain (le dashboard compte toujours les événements à 0), mais les includes CRS sont strippés. Coût d'évaluation CRS à zéro.
À utiliser quand : une règle précise (ex. 942100 SQLi libinjection) fire sur un payload légitime dans beaucoup de champs différents — sinon préfère l'option (0).
- Édite la route → WAF exclusions → Excluded rule IDs
- Liste séparée par virgules :
942100, 920170, 911100 - Save
La règle est retirée de l'évaluation CRS pour cette route. Les autres règles de la même famille s'appliquent toujours.
À utiliser quand : une famille de règles entière est bruyante sur cette route. Exemple : attack-protocol (range de règles 911-921) trip sur l'usage légitime de WebDAV / méthodes HTTP non-standard.
- Édite la route → WAF exclusions → Excluded tags
- Choisis dans le dropdown autocomplete :
attack-protocol,attack-sqli,attack-rce,language-php,paranoia-level/3, ... - Save
Toute la famille de tags est exclue. Les règles avec ce tag sont retirées de l'évaluation CRS pour cette route. Plus cheap à maintenir que de lister 15 IDs de règles.
Bloque les requêtes qui correspondent à tes propres critères, sans écrire de SecLang. Formulaire de route → section WAF → Règles personnalisées.
Une règle a un nom et 1 à 8 conditions qui doivent toutes correspondre. Chaque condition accepte plusieurs valeurs (une par ligne — l'une ou l'autre suffit) :
| Critère | Opérateurs | Exemple |
|---|---|---|
| Chemin | commence par · est · contient | commence par /wp-admin
|
| Méthode | est · n'est pas | n'est pas GET / HEAD / POST
|
| User-Agent | contient · est · est absent ou vide | contient sqlmap / nikto (sans tenir compte des majuscules) |
| En-tête (par nom) | est absent · est présent · contient · est |
X-Admin-Token est absent |
L'éditeur affiche la règle en phrase pendant la saisie (« Bloquer si la méthode est POST et le chemin commence par /login et le User-Agent est absent ou vide »). Trois modèles la pré-remplissent : Fichiers sensibles (/.env, /.git…), Scanners connus (User-Agents), Méthodes limitées.
Fonctionnement :
- Une règle suit le mode WAF de la route : Block → 403, Detect → la requête passe et la correspondance est journalisée. Les règles n'ont pas d'effet quand le WAF est off (elles sont conservées).
- Les règles personnalisées passent avant l'OWASP CRS et fonctionnent aussi quand le CRS est désactivé sur la route.
- Chaque règle peut être désactivée sans être supprimée. Les règles sont enregistrées avec la route (Enregistrer).
- Dans l'historique WAF (Journaux, page Sécurité de la route), les événements ont la catégorie CUSTOM et le nom de la règle. Les IDs de règles sont
120000–129999et ne changent pas quand tu modifies une règle. - L'IP source / le réseau n'est pas un critère : utilise pour cela le filtrage IP des règles par chemin.
Pour ce que les règles guidées ne savent pas exprimer, chaque route a un éditeur SecLang (formulaire de route → WAF → SecLang (avancé), replié tant qu'il est vide). C'est la vraie syntaxe Coraza / ModSecurity, avec des garde-fous et de l'aide :
-
Éditeur : coloration, autocomplétion (directives, variables,
@opérateurs, actions, valeurs dectl:ett:— uniquement ce qui est autorisé), vérification en direct avec le problème affiché sur sa ligne. -
Modèles, commentés ligne par ligne dans ta langue, insérés avec le prochain ID libre : API en JSON uniquement, méthodes autorisées,
/admindepuis le réseau local seulement, exiger un en-tête de clé API, bloquer des robots par User-Agent, limiter la longueur des paramètres, exception pour un champ. - → SecLang sur une règle guidée : la remplace par le SecLang qu'elle génère (bonne façon d'apprendre, puis d'aller plus loin).
- Tester une requête (routes enregistrées) : méthode, chemin, en-têtes, corps → « Bloquée (405) par la règle 130000 Méthode non autorisée » ou « Acceptée », avec toutes les règles déclenchées (OWASP CRS compris). Utilise le contenu non enregistré de l'éditeur, n'envoie rien au backend et ne journalise rien.
Ce qui est autorisé (vérifié à l'enregistrement ; un refus indique la ligne) :
- Directives
SecRule,SecAction,SecMarkeruniquement — pas d'Include, pas de réglage du moteur (SecRuleEngine,SecRequestBodyLimit…), pas de journal d'audit, pas de fichiers de données. - IDs de règles 130000–139999, un par règle (les maillons d'une chaîne n'en ont pas), uniques.
- Opérateurs :
@rx @streq @beginsWith @endsWith @contains @pm @within @eq @ge @gt @le @lt @ipMatch @detectSQLi @detectXSS @validateByteRange @validateUrlEncoding @validateUtf8Encoding @strmatch @unconditionalMatch @noMatch. Refusés entre autres :@inspectFile(lance un programme),*FromFile(lit des fichiers de l'hôte),@rbl(réseau),@geoLookup,@validateSchema. - Actions :
id phase chain deny block drop pass allow status redirect log nolog auditlog noauditlog msg logdata tag severity rev ver maturity capture multiMatch setvar expirevar skip skipAfter t ctl. Refusées :exec,setenv,initcol.ctl:limité à la familleruleRemove…,requestBodyProcessoretforceRequestBodyVariable(pas dectl:ruleEngine, pas de changement des limites de corps). - La collection
ENVet les macros%{ENV.…}sont refusées (l'environnement du processus peut contenir des secrets). - 64 Kio et 200 règles maximum.
Le SecLang s'exécute après les exclusions ciblées et les règles guidées et avant le CRS, suit le mode de la route (la ligne SecRuleEngine reste celle d'Arenet) et fonctionne aussi quand le CRS est désactivé. Les événements ont la catégorie CUSTOM et le msg de la règle comme nom. Préfère les règles guidées quand elles suffisent : elles ne peuvent pas être mal écrites.
Arenet est un simple binaire Go — il n'exécute ni PHP ni Node, donc ce type de CVE d'interpréteur ne touche jamais Arenet lui-même. Mais en tant que reverse proxy devant tes applis, il peut en virtual-patcher certaines pour les backends situés derrière, le temps que tu mettes le backend à jour. C'est de la défense en profondeur, pas un remplacement du correctif de l'appli vulnérable.
Deux cas réels de la vague « Black October » d'octobre 2026. Colle la règle dans l'éditeur SecLang (avancé) d'une route, démarre la route en detect, confirme avec Tester une requête, puis passe en block. Les règles sont par route : limite-les aux routes qui exposent réellement l'appli concernée.
Écriture hors limites dans le décodeur Brotli embarqué de Node : un seul corps de requête encodé en Brotli qui atteint le décodeur (Express 5 / body-parser 2.x décodent Content-Encoding: br par défaut, donc express.json() suffit) donne une RCE. Les clients n'encodent quasiment jamais les corps de requête en Brotli, donc les rejeter en bordure supprime la surface d'attaque entrante avec un impact négligeable :
SecRule REQUEST_HEADERS:Content-Encoding "@rx (?i)(^|[,\s])br([,;\s]|$)" "id:130201,phase:1,deny,status:415,log,msg:'Block Brotli-encoded request body (Node2Shell CVE-2026-111497)',tag:'cve-2026-111497'"
Rejette Content-Encoding: br (et gzip, br) en phase 1, avant la lecture du corps, avec un 415. Limite : ne couvre que les corps de requête entrants — il ne protège pas le contenu que ton appli va elle-même chercher et décode en Brotli depuis un serveur externe (fetch() / undici). Mets Node à jour en 22.23.4 / 24.22.1 / 26.12.1.
Bugs de corruption mémoire dans le cœur de l'interpréteur PHP (un use-after-free atteint via session.upload_progress de PHP, plus un chemin out-of-memory), les deux en RCE non authentifiée. Le bug se situe sous le userland, donc une signature WAF ne peut pas être un correctif fiable — elle ne fait qu'élever la barre face au chemin d'exploitation public connu, qui s'appuie sur un champ multipart nommé PHP_SESSION_UPLOAD_PROGRESS :
SecRule ARGS_NAMES "@rx (?i)PHP_SESSION_UPLOAD_PROGRESS" "id:130202,phase:2,deny,status:403,log,msg:'Block PHP session.upload_progress abuse (PHP2Shell CVE-2026-109619)',tag:'cve-2026-109619'"
À lire avant de déployer :
- Cela bloque une technique d'exploitation, pas la vulnérabilité. CVE-2026-109618 (chemin OOM) n'a pas de signature de requête fiable ; le seul levier brut y est une limite de taille du corps sur l'endpoint d'upload.
-
PHP_SESSION_UPLOAD_PROGRESSest une fonctionnalité légitime de PHP. Si une appli derrière la route utilise réellement l'upload progress, cette règle casse ces uploads — d'où le périmètre par route et le detect d'abord. - Rien ici ne remplace la mise à jour de PHP vers la version corrigée.
Les deux règles ont été validées contre l'allowlist SecLang d'Arenet et le moteur Coraza : elles passent la vérification de l'éditeur, bloquent la forme d'attaque, et laissent passer une requête normale. Lance Tester une requête sur ta route pour le constater avant de passer en block.
OWASP CRS supporte quatre paranoia levels (PL1–PL4) qui contrôlent la strictness :
- PL1 (défaut) : règles avec faible taux de faux positifs, adapté à la plupart des web apps publics
- PL2 : ajoute des règles qui peuvent tripper sur des edge cases légitimes
- PL3 : agressif, attends-toi à des FPs sur des apps à contenu riche
- PL4 : maximum, pour endpoints hardenés uniquement
Arenet émet actuellement PL1 par défaut pour chaque route. La configuration paranoia level par route n'est pas exposée dans l'UI dès v2.9.3 ; si tu as besoin de PL2+ sur une route spécifique, le workaround est des directives Coraza custom via la config WAF de la route (avancé, pas exposé UI).
- Le handler WAF tourne avant le handler reverse-proxy donc un rejet block-mode n'atteint jamais l'upstream
- Les instances WAF sont poolées par leur SHA de directives-string — N routes avec une config WAF identique partagent une seule instance Coraza WAF en mémoire (voir la clé de pool dans
internal/waf/module.go) - L'inspection de body (multipart, XML, JSON) est le chemin le plus lourd. Pour les routes qui handle des gros uploads (serveurs de fichiers, Docker registry), active le Upload streaming mode sur la route qui skip l'inspection de body et demande à Caddy de flush les bytes sans buffering RAM
- Le moteur Coraza lui-même est Go-native (pas de cgo), donc pas d'overhead de thread par requête
| Symptôme | Cause probable | Fix |
|---|---|---|
| GET avec body retourne 403 sur API REST |
920170 (attack-protocol) |
Exclure règle 920170 ou tag attack-protocol
|
| PUT/DELETE retourne 403 sur API admin |
911100 (METHOD-ENFORCEMENT) |
Exclure règle 911100 ou tag attack-protocol
|
Tout ce qui contient -- dans l'URL retourne 403 |
942100 (SQLi libinjection FP sur UUIDs / version strings) |
Exclure règle 942100 ou, plus étroit, exclure uniquement sur la route spécifique |
| Upload multipart retourne 403 |
922xxx (attaques multipart) |
Exclure tag attack-multipart ou activer Upload Streaming Mode |
Authentik / Keycloak /api/v1/auth/oidc/callback retourne 403 |
Plusieurs règles CRS tripent sur les params OAuth state/code | Exclure tags attack-protocol + attack-sqli sur la route IdP, OU set le WAF de la route IdP à off
|
Le workflow recommandé pour ajouter une nouvelle route :
- Set WAF mode à
detectd'abord - Utilise la route normalement pendant 24-48h (ton vrai trafic exercise le set de règles CRS)
- Ouvre
/security/<routeId>→ review les événements générés - Identifie les FPs : événements où le payload qui a matché est de la donnée user légitime, pas une attaque
- Ajoute les exclusions (IDs de règles ou tags) pour les FPs
- Switch WAF mode à
block - Câble une alerte (Alerting) sur
waf_event_rate > 10 / 5minpour catcher les spikes inattendus
Le dashboard /dashboard montre les taux WAF detect/block par top-N routes. La page /security agrège les événements à travers toutes les routes avec filtres. Le /logs unifié montre les événements WAF à côté des événements auth + rate-limit + cert pour corrélation ("est-ce que cette IP a tenté N probes WAF ET échoué auth N fois ET trip rate-limit ?").
- Routes — où trouver les settings WAF dans l'UI
- CrowdSec — bannit les IPs WAF-triggering les plus égregious à la couche réseau (avant qu'elles atteignent le WAF)
-
Alerting — alerte sur les spikes de
waf_event_rate - Custom Error Pages — brande la page 403 que le WAF retourne
- Documentation OWASP CRS — référence des règles + taxonomie des tags
- Documentation Coraza — internes du moteur WAF
-
internal/waf/category.go— mapping ID de règle Arenet → catégorie -
internal/caddymgr/manager.go(lignes ~1400-2550) — emit du handler WAF + shape SecAction par route
- Home · Accueil
- Installation · Installation 🇫🇷
- Routes · Routes 🇫🇷
- TCP / UDP services · Services TCP / UDP 🇫🇷
- DNS Providers · DNS Providers 🇫🇷
- Topology · Topology 🇫🇷
- WAF · WAF 🇫🇷
- CrowdSec · CrowdSec 🇫🇷
- Country Block · Country Block 🇫🇷
- Rate Limit · Rate Limit 🇫🇷
- OIDC SSO · OIDC SSO 🇫🇷
- Forward auth · Forward auth 🇫🇷
- Certificates · Certificats 🇫🇷
- Updating Arenet · Mettre à jour 🇫🇷
- Backup & Restore · Backup & Restore 🇫🇷
- Alerting · Alerting 🇫🇷
- Custom Error Pages · Custom Error Pages 🇫🇷
- API (OpenAPI) · API (OpenAPI) 🇫🇷
- Troubleshooting · Troubleshooting 🇫🇷