Aller au contenu principal
Xsec

Internals des WAF modernes : architecture, évasion et retour de production NyxR

Publié le 44 min de lecture

Mis à jour le

Un Web Application Firewall (WAF), ou pare-feu applicatif web, analyse le trafic du protocole Hypertext Transfer Protocol (HTTP) avant qu’il n’atteigne le serveur d’origine, c’est-à-dire le service applicatif final. Un WAF moderne est stateful : il conserve un état entre plusieurs événements afin de corréler une requête avec un client, une politique et un historique. Il doit aussi canonicaliser chaque message, donc réduire ses représentations équivalentes à une forme commune, exactement comme les composants en aval. Si le proxy, intermédiaire qui reçoit puis relaie la requête, le WAF et l’application ne donnent pas le même sens aux mêmes octets, le moteur de détection ne protège plus le message consommé par l’origine.

Le 23 juin 2026, entre 19 h 35 et 19 h 40, heure de Paris (UTC+2), la passerelle de sécurité NyxR a enregistré sur mon application nyxr.app 3 119 requêtes provenant d’un scanner. En cinq minutes, celui-ci a parcouru 2 100 Uniform Resource Identifiers (URI), les identifiants de ressources visés par les requêtes. Le data plane, ensemble des composants qui interprètent et décident chaque requête synchroniquement avant l’envoi de la réponse, en a bloqué 3 117, soit 99,94 %, avec une latence de 0 ms au 50e percentile ou médiane (p50) et au 95e percentile (p95). ModSecurity, moteur WAF open source, a produit en parallèle 3 121 transactions d’audit associées à la règle 913100 de l’Open Worldwide Application Security Project Core Rule Set (OWASP CRS), un jeu de règles générique qui reconnaît ici le User-Agent, chaîne par laquelle un client déclare son logiciel, d’un scanner connu.

Reconnaître un scanner connu n’est pourtant que la partie visible du résultat. Pour qu’un tel blocage soit fiable, un WAF moderne doit aussi reconstruire la bonne adresse client derrière les proxys, refuser les messages HTTP ambigus, exécuter les contrôles dans le bon ordre, appliquer la politique localement et journaliser la décision sans compter deux fois la même transaction. Une erreur dans l’une de ces couches peut créer un bypass, donc une requête qui échappe au contrôle, ou un faux positif, c’est-à-dire un trafic légitime classé à tort comme malveillant.

J’ai conçu NyxR autour de cette chaîne. Cet article expose les choix d’architecture qui en découlent, de la délimitation du message à la décision finale, puis vérifie leur comportement sur un mois de métriques produites par mon application nyxr.app.

Architecture et chaîne de confiance

Le modèle historique du WAF est celui d’un filtre : la requête entre, des expressions la comparent à des signatures, puis elle est acceptée ou rejetée. Ce modèle explique un moteur de règles, mais pas un système moderne.

Une requête réelle traverse plusieurs interprètes. Le client sérialise un message. Un Content Delivery Network (CDN), réseau distribué de caches et de proxys, peut le recevoir en premier ; un répartiteur de charge (load balancer) distribue ensuite le trafic entre plusieurs serveurs, tandis qu’un proxy inverse (reverse proxy) relaie la requête au nom de l’origine. Chacun peut la décoder, la normaliser, la convertir de HTTP/2 vers HTTP/1.1, ajouter des en-têtes et réutiliser une connexion vers le backend, service situé en aval.

Le WAF construit ensuite ses propres collections de variables. Enfin, le framework applicatif décode l’Uniform Resource Locator (URL), l’adresse de la ressource demandée, ainsi que les cookies, formulaires et corps structurés. JavaScript Object Notation (JSON) est un format texte de données structurées ; Extensible Markup Language (XML) est un langage à balises ; un corps multipart sépare plusieurs parties, notamment champs et fichiers, par des délimiteurs. La sécurité dépend de l’accord entre tous ces interprètes.

Une Request for Comments (RFC) est une spécification technique publiée dans l’écosystème de l’Internet Engineering Task Force (IETF), organisme qui standardise les protocoles Internet. Le RFC 9110 sépare la sémantique HTTP de sa représentation. Le RFC 9112 définit le cadrage HTTP/1.1 et avertit que des interprétations différentes facilitent le request smuggling, attaque qui fait délimiter le même flux de requêtes différemment par deux composants successifs. HTTP/2 et HTTP/3 déplacent ce cadrage dans des trames binaires, mais les conversions vers un backend HTTP/1.1 réintroduisent une frontière d’interprétation.

Un WAF moderne doit donc répondre à cinq questions distinctes :

  1. Où la requête commence-t-elle et où finit-elle ? C’est le problème de cadrage et de parsing, l’analyse structurale du message.
  2. Qui ou quoi l’a probablement envoyée ? C’est le problème d’identité et de confiance dans les proxies.
  3. Que signifie son contenu après transformations ? C’est le problème de canonicalisation et de détection.
  4. Que révèle sa relation avec les requêtes précédentes ? C’est le problème temporel et comportemental.
  5. Quelle action reste sûre en cas de panne ? C’est le problème d’architecture et de mode dégradé.

La détection n’est qu’une étape. Le parsing, la normalisation, l’enrichissement, la décision, l’enforcement, application effective de l’action, la journalisation et l’apprentissage doivent rester séparés.

Ces responsabilités n’ont ni la même échéance ni le même mode de panne. Les organiser en plans distincts permet de garder la décision courante locale tout en faisant évoluer la politique et l’analyse sur des cycles plus longs.

Trois plans, pas un monolithe. Dans une architecture de WAF moderne, un plan est un groupe logique de composants organisé autour d’une responsabilité et d’un rythme d’exécution ; il ne correspond pas nécessairement à une machine distincte. Le data plane prend la décision courante : il termine la connexion, établit le contexte client, applique les contrôles locaux, inspecte le contenu, puis bloque ou relaie la requête. Le control plane gère la publication de la politique. Il reçoit l’état désiré, description déclarative de la politique attendue par l’opérateur, le valide, le versionne et le transforme en configuration exécutable sans intervenir dans la requête courante. Le plan d’observation reçoit une copie des décisions après leur application, conserve l’historique, corrèle les événements et propose des évolutions. Une proposition ne peut influencer une requête future qu’après être repassée par le control plane ; elle ne réécrit jamais la décision déjà prise. J’ai conçu NyxR selon cette séparation, comme une Unified Web Security Gateway, passerelle auto-hébergée qui regroupe proxy inverse, WAF et contrôles comportementaux devant plusieurs services.

Le chemin critique, ou hot path, est la partie du data plane composée de toutes les opérations qu’une requête doit attendre avant d’obtenir sa réponse. Une base ou un service distant placé sur ce chemin devient donc une dépendance directe de latence et de disponibilité. Dans l’implémentation NyxR, le hot path combine NGINX, serveur web et proxy inverse, OpenResty, distribution de NGINX qui embarque le langage de script Lua via LuaJIT, moteur qui compile Lua à l’exécution, puis libModSecurity v3 et OWASP CRS. Transport Layer Security (TLS) chiffre la connexion entre deux pairs ; son extension Server Name Indication (SNI) annonce le nom d’hôte visé pendant la négociation afin de sélectionner le bon certificat et le bon service.

Hors de ce chemin, le control plane écrit l’état désiré dans PostgreSQL, base de données relationnelle, et le code TypeScript, variante typée de JavaScript, construit un bundle, c’est-à-dire un paquet complet de configuration prêt à activer. Le plan d’observation transporte les événements via Redis Streams, journal ordonné en mémoire fourni par Redis, puis un scorer convertit des signaux pondérés en score. Une règle en mode shadow, ou mode d’observation, calcule et journalise ce qu’elle aurait décidé sans modifier la réponse envoyée au client. Les indicateurs de Cyber Threat Intelligence (CTI) sont des données de menace connues, par exemple des adresses associées à une infrastructure malveillante.

Schéma technique
TROIS PLANS, UN POINT D'ENFORCEMENT: Le chemin de requête reste local, la configuration et l'analyse évoluent séparément.NYXR · SÉPARATION DES RESPONSABILITÉSTROIS PLANS, UN POINT D'ENFORCEMENTLe chemin de requête reste local, la configuration et l'analyse évoluent séparément.PLAN DE DONNÉES · SYNCHRONEPLAN DE CONTRÔLEPLAN D'OBSERVATION / APPRENTISSAGEoriginebundleévénementsCLIENTHTTP / TLSrequêteEDGEOpenRestySNI · IP réelleGATES LOCAUXPolitique LuaCTI · débit · botfingerprintWAFModSecurity+ CRSparser · scorerÉTAT DÉSIRÉPostgreSQLservices · règlesPUBLIERConfigbuildertester · basculerPREUVESLogs structurésaction · raison · scoreTRANSPORTRedis Streamsflux live bornéHISTORIQUEÉvénements sécuritésnapshot terrainCORRÉLERScorerdécroissance · preuvesAPPRENDREProfilstest sans blocageCHEMIN CRITIQUE · ÉTAT LOCAL · POLITIQUE VERSIONNÉE · PREUVES ASYNCHRONES · MODES DE PANNE
Architecture NyxR, du chemin de requête à la configuration et à l'observation.Lecture du schémaLisez le plan de données de gauche à droite. Il prend la décision synchrone avec un état local. Le plan de contrôle publie des bundles versionnés et validés. Le plan d'observation reçoit une copie des décisions et peut enrichir la politique future sans devenir une dépendance de la requête courante.

Cette séparation répond à une contrainte fondamentale : la décision synchrone ne doit pas appeler PostgreSQL, un modèle d’intelligence artificielle ou un datastore analytique, stockage optimisé pour agréger et interroger des événements, à chaque requête. Une Access Control List (ACL), ou liste de contrôle d’accès, est un ensemble de règles qui autorisent ou refusent un trafic selon des critères explicites. Une adresse Internet Protocol (IP) identifie une interface sur un réseau IP ; la notation Classless Inter-Domain Routing (CIDR) associe une adresse à une longueur de préfixe pour désigner un réseau entier, par exemple 192.0.2.0/24. Dans un WAF moderne, les ACL nécessaires à la requête doivent être évaluées localement dans le hot path afin de bloquer ou laisser passer sans dépendre d’une base distante. Un snapshot est une copie cohérente et figée à un instant donné. NyxR applique ce principe aux ACL, aux snapshots de réputation, aux compteurs et aux autres politiques de décision. Une panne du control plane laisse ainsi le dernier bundle valide en service.

Cette autonomie locale n’est réelle que si une politique peut être publiée sans exposer de configuration partielle. Le mécanisme d’activation doit donc préserver un dernier état valide et rendre chaque changement vérifiable avant qu’il n’atteigne le data plane.

Le config builder, composant qui compile l’état désiré en fichiers exécutables, ne modifie pas le fichier actif ligne par ligne. Il rend un bundle complet, effectue des contrôles structurels et nginx -t, calcule une somme de contrôle (checksum) pour détecter toute différence d’octets, écrit une nouvelle version puis bascule le lien actif atomiquement, donc sans état intermédiaire visible. Il recharge enfin la passerelle et exécute un contrôle de santé (health check) ; une activation malsaine restaure la version précédente.

Cette architecture apporte trois garanties utiles à un pentester :

  • la version de politique qui a servi une requête peut être reliée à la décision ;
  • un contournement du control plane n’implique pas un contournement immédiat du data plane ;
  • une dépendance analytique indisponible ne devrait pas modifier silencieusement la grammaire HTTP acceptée.
WarningFail-open ou fail-closed

Elle apporte aussi une question de test : le dernier bundle connu est-il réellement autonome, ou une dépendance distante cachée peut-elle transformer une panne en fail-open, mode dans lequel le trafic est autorisé lorsque le contrôle échoue ? À l’inverse, un système fail-closed refuse le trafic pendant cette panne.

Du socket à la décision

La séparation des plans indique qui publie la politique, qui décide et qui observe. Elle ne suffit pas à montrer dans quel ordre le data plane acquiert les informations nécessaires à une décision ; il faut donc suivre une requête depuis la connexion brute jusqu’à l’action finale.

Une socket réseau est l’extrémité logicielle par laquelle le serveur reçoit une connexion. À partir de cette socket, une requête ne doit pas être pensée comme une chaîne passée à une fonction is_malicious() : elle évolue à mesure que de nouvelles informations deviennent disponibles. Une empreinte (fingerprint) résume des traits techniques du client sans prétendre l’identifier ; le rate limiting borne le nombre de requêtes dans une fenêtre de temps ; un challenge exige une preuve supplémentaire, souvent l’exécution d’un navigateur, avant de poursuivre.

Construire le contexte de décision

Schéma technique
UNE REQUÊTE N'EST PAS UNE CHAÎNE: Cadrage, identité, historique, contenu et coût s'évaluent à des étapes différentes.CYCLE DE REQUÊTE · CONFIANCE ORDONNÉEUNE REQUÊTE N'EST PAS UNE CHAÎNECadrage, identité, historique, contenu et coût s'évaluent à des étapes différentes.CHEMIN SYNCHRONE DE LA REQUÊTEPREUVES ÉMISES APRÈS LA DÉCISIONlog1 · CADRAGEParser HTTPcadrage strict2 · IDENTITÉIP réellehops approuvés3 · CONTEXTEACL + CTIgeo · bans4 · COMPORTEMENTFingerprint+ compteursjournal ou blocage5 · CONTENUModSecurity+ CRSphases · score6 · FRICTIONLimiter ouchallenger7AppDÉCISIONautorisé · bloquéchallengé · banniATTRIBUTIONaction ≠ raisontest ≠ blocagePROVENANCErègle · sourceversion de configPERFORMANCEedge + originelatences séparéesREJETER L'AMBIGUÏTÉ · ÉTABLIR L'IDENTITÉ · GATES RAPIDES · PARSER LE CORPS · JOURNALISER L'ACTION
Chemin critique conceptuel. Les handlers exacts et les modes dépendent de la politique du service.Lecture du schémaLa requête doit d'abord avoir une frontière non ambiguë et une identité source fiable. Des gates locales peu coûteuses peuvent ensuite rejeter un risque connu avant l'inspection du corps. CRS parse et score la transaction, tandis que challenge et rate limiting traitent l'automatisation et le coût qu'une signature de payload ne peut pas exprimer.

1. Terminaison TLS et cadrage HTTP.

La terminaison TLS déchiffre la connexion sur la passerelle et révèle le message HTTP. Le ClientHello est le premier message envoyé par le client pendant la négociation TLS ; ses versions, suites cryptographiques et extensions exposent des traits de sa pile réseau. Cette terminaison fixe aussi une frontière de confiance : un attaquant qui peut joindre directement l’origine contourne tous les contrôles situés à l’edge, la couche périmétrique exposée à Internet. L’authentification mutual TLS (mTLS) vérifie par certificat le serveur et le client ; avec la restriction réseau ou un secret d’origine, elle empêche qu’un client non autorisé contourne la passerelle.

Avant d’inspecter un payload, le contenu utile transporté par la requête, la passerelle doit rejeter les messages dont la longueur ou la structure pourrait être comprise différemment en aval. Les travaux HTTP Desync Attacks, The HTTP Garden et WAFFLED montrent que les divergences de parseur ne sont pas un cas académique. WAFFLED rapporte 1 207 contournements confirmés sur cinq WAF largement utilisés en modifiant des éléments non malveillants de JSON, XML et multipart.

Un WAF moderne doit rejeter les ambiguïtés de cadrage avant ses décisions de confiance. Dans NyxR, j’ai placé un garde de désynchronisation local à cet endroit précis. Son rôle n’est pas de détecter une injection Structured Query Language (SQL), où une entrée modifie la structure d’une requête adressée à une base de données, mais de refuser une requête avant qu’un proxy et une origine puissent lui donner deux significations.

Le cadrage établit une représentation non ambiguë de la requête, mais pas l’identité réseau à laquelle rattacher la décision. Une fois le message délimité, la chaîne de confiance doit déterminer quelle adresse peut alimenter la réputation, les quotas et les bannissements.

2. Adresse IP réelle et chaîne de confiance.

L’en-tête X-Forwarded-For (XFF) transporte la chaîne d’adresses déclarées par les proxys traversés ; il n’est pas une identité, mais une affirmation. Le module NGINX ngx_http_realip_module ne la transforme en adresse client qu’après configuration de sources de confiance avec set_real_ip_from. Si tout Internet est approuvé, l’attaquant choisit sa réputation, son pays, sa clé de limitation et parfois sa capacité à contourner une allowlist, liste explicite de sources autorisées.

Le bon invariant pour un WAF moderne est : seul un saut réseau (hop) explicitement approuvé peut remplacer l’adresse reçue sur la socket. Dans NyxR, je pars d’un ensemble de confiance vide, je publie les CIDR des proxys approuvés dans le bundle et j’applique un garde supplémentaire. IPv4 et IPv6 sont les deux versions déployées du protocole IP, avec des espaces d’adressage et parfois des routes différentes. Le protocole PROXY transporte hors des en-têtes HTTP l’adresse de connexion originale entre deux proxys qui se font confiance. Pour un red teamer, le test porte sur chacun de ces chemins, mais aussi sur l’accès direct, le CDN, le répartiteur de charge et le réseau d’administration.

Une identité réseau suffisamment fiable rend les décisions indexées par adresse exploitables. Le data plane peut alors appliquer les contrôles locaux les moins coûteux avant de réserver l’inspection profonde aux requêtes restantes.

3. Garde-fous locaux à faible coût.

Les ACL, la géolocalisation, les indicateurs CTI, les bannissements actifs et certaines réputations peuvent être évalués avant l’inspection profonde du corps. Cela économise du temps de processeur, de la mémoire et de la bande passante vers l’origine. Cette priorité n’est sûre que si les dérogations sont explicites : une allowlist doit préciser quels contrôles elle contourne et lesquels restent obligatoires.

Ces garde-fous écartent les sources ou contextes déjà connus, mais une requête isolée peut rester banale. La corrélation comportementale ajoute alors le temps, les erreurs et la séquence des routes afin de reconnaître une automatisation répartie sur plusieurs transactions.

4. Comportement et empreinte client.

L’IP, l’Autonomous System Number (ASN), numéro du réseau qui annonce l’adresse sur Internet, la famille de User-Agent, l’ordre des en-têtes, la version HTTP, le résultat des challenges, le débit, les réponses HTTP 404, les erreurs d’authentification et les routes parcourues ont des durées de vie différentes. Leur corrélation permet de traiter un robot distribué qu’aucun payload isolé ne trahit.

Le comportement décrit comment un client agit dans le temps, pas ce que contient la transaction courante. ModSecurity et CRS apportent cette inspection du message normalisé en recherchant des motifs dans ses variables, ses en-têtes et son corps.

5. Inspection ModSecurity et CRS.

Le WAF parse les variables disponibles, applique des transformations déterministes, exécute des règles et accumule un score d’anomalie. Ce score est la somme des points ajoutés par les règles qui correspondent à la transaction, pas une probabilité d’attaque. Les limites de corps, de profondeur JSON et de nombre d’arguments font partie du contrôle : une erreur de parsing ignorée crée une zone que l’origine comprend mais que le WAF ne comprend pas.

L’inspection produit des correspondances et un score, mais elle ne choisit pas seule l’effet visible par le client. La dernière étape confronte ces signaux à la politique afin de décider entre transmission, limitation, challenge, bannissement ou refus.

6. Friction et décision finale.

Un blocage n’est pas toujours la meilleure réponse. Un challenge peut séparer un navigateur interactif d’un client automatisé. Une limite de débit peut protéger un endpoint, combinaison d’une méthode HTTP et d’une route applicative, sans prétendre que chaque requête est malveillante. Un bannissement temporaire peut amortir une séquence. Le journal final doit conserver séparément l’action réellement appliquée et la raison candidate.

Ce parcours situe le moteur de règles parmi plusieurs contrôles indépendants. Pour interpréter correctement ses résultats, il reste à comprendre quand une règle s’exécute, quelles variables elle voit et comment ses points deviennent éventuellement une action disruptive.

Interpréter ModSecurity et OWASP CRS

ModSecurity v3 exécute les règles dans cinq phases documentées : en-têtes de requête, corps de requête, en-têtes de réponse, corps de réponse et journalisation. Les données sont cumulatives. Une règle de phase 1 ne voit pas encore tous les arguments du corps ; une règle de phase 5 ne peut plus bloquer la transaction. La référence ModSecurity v3 décrit ces contrats et les différences de support par rapport à v2.

Les familles montrées ci-dessous couvrent notamment la Local File Inclusion (LFI), inclusion d’un fichier local, la Remote Code Execution (RCE), exécution de code à distance, le Cross-Site Scripting (XSS), injection de code exécuté dans le navigateur, et la SQL injection (SQLi). Le Paranoia Level (PL) choisit la sensibilité des règles activées. Le mode DetectionOnly conserve les détections sans appliquer d’action disruptive, tandis que le mode On autorise notamment le refus de la requête.

Schéma technique
DÉTECTION ET BLOCAGE SONT SÉPARÉS: Les règles ajoutent des preuves, une politique compare ensuite le score cumulé au seuil.MODSECURITY · DÉTECTION COLLABORATIVE CRSDÉTECTION ET BLOCAGE SONT SÉPARÉSLes règles ajoutent des preuves, une politique compare ensuite le score cumulé au seuil.INSPECTION DE LA REQUÊTEPOLITIQUE / RÉSULTATmodePLexclurePHASE 1En-têtesméthode · URI · cadrePROCESSEURParser du corpsJSON · form · XMLPHASE 2Argumentscibles normaliséesCRS RULESProtocol · LFIRCE · XSS · SQLiajout de pointsÉVALUATIONRule 949score ≥ seuilRÉGLAGENiveau de paranoïacouverture vs bruitEXCEPTIONURI / paramètreretrait cibléDÉTECTERPreuve d'auditsans disruptionBLOQUERRéponse localel'app n'est pas jointeORDRE DES PHASES · TRANSFORMATIONS · ERREURS DE PARSING · SCORE D'ANOMALIE · PORTÉE DES EXCLUSIONS
Flux entrant simplifié de ModSecurity v3 et CRS.Lecture du schémaLa phase 1 inspecte les en-têtes et peut sélectionner un processeur de corps. La phase 2 reçoit les arguments parsés si le traitement réussit. Les règles CRS ajoutent des points d'anomalie pondérés par sévérité. L'évaluation de blocage compare ensuite le score entrant cumulé au seuil. DetectionOnly conserve la preuve sans appliquer l'action disruptive.

Les phases déterminent les données accessibles à chaque règle, mais pas la manière dont plusieurs correspondances sont agrégées. Le score d’anomalie fournit cette couche de composition avant l’évaluation du seuil de blocage.

Le score d’anomalie n’est pas une probabilité.

CRS utilise une détection collaborative. Une règle critique ajoute typiquement 5 points, une erreur 4, un avertissement 3 et une notice 2. Les règles de détection sont séparées de l’évaluation de blocage. La documentation Anomaly Scoring explique que REQUEST-949-BLOCKING-EVALUATION.conf compare le score entrant total au seuil configuré.

Un score de 10 ne signifie ni 10 % de risque ni une attaque deux fois plus probable qu’un score de 5. Il signifie que des règles ont ajouté dix points selon une convention de politique. La bonne preuve conserve au minimum :

  • les identifiants de règles qui ont matché ;
  • les variables et transformations examinées ;
  • le niveau de paranoïa exécuté ;
  • le seuil d’anomalie ;
  • le mode DetectionOnly ou On ;
  • l’action finale du gateway.

Le score explique comment les règles contribuent à une transaction, mais sa sensibilité dépend encore des règles activées et de leur contexte. Le niveau de paranoïa élargit la couverture, tandis que les exclusions retirent précisément les cibles légitimes qui provoquent des faux positifs.

Niveau de paranoïa et exclusions.

Les niveaux de paranoïa activent des règles de plus en plus sensibles de PL1 à PL4. Augmenter le seuil pour compenser un PL plus élevé peut masquer une règle critique isolée. La stratégie recommandée est de choisir le PL selon le risque, garder un seuil bas, observer les faux positifs puis écrire des exclusions étroites.

Une exclusion sûre vise une règle, une URI et si possible un paramètre. Désactiver toute la famille SQLi parce qu’un champ Markdown contient le mot select résout le faux positif en supprimant la protection. L’exemple générique suivant ne reproduit pas la configuration de NyxR : REQUEST_URI désigne la route reçue, ARGS la collection des paramètres et REQBODY_ERROR l’indicateur d’échec du parseur de corps.

crs-tuning.conf
# Retirer uniquement le paramètre "content" de la règle sur cet endpoint.
SecRule REQUEST_URI "@beginsWith /api/articles" \
"id:100100,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:content"
# Conserver une erreur de parsing comme événement bloquant.
SecRule REQBODY_ERROR "!@eq 0" \
"id:100110,phase:2,t:none,log,deny,status:400,msg:'Request body parsing failed'"
# Publier explicitement la politique de scoring de la transaction.
SecAction "id:100120,phase:1,pass,nolog,t:none,\
setvar:tx.blocking_paranoia_level=2,\
setvar:tx.inbound_anomaly_score_threshold=5"

Les identifiants 100100 à 100120 sont réservés ici à l’exemple. En production, ils doivent appartenir à une plage locale documentée et ne pas entrer en collision avec CRS.

Les exclusions règlent le périmètre des variables que le moteur sait analyser ; elles ne donnent aucune visibilité sur un corps tronqué, surdimensionné ou impossible à parser. Les plafonds de ressources et la politique appliquée lorsqu’ils sont dépassés constituent donc une frontière de sécurité distincte.

WarningLes limites sont des décisions de sécurité

Les directives ModSecurity SecRequestBodyLimit, SecRequestBodyNoFilesLimit, SecArgumentsLimit et SecRequestBodyJsonDepthLimit bornent respectivement la taille du corps, sa partie hors fichiers, le nombre d’arguments et la profondeur JSON. Si la politique traite seulement le début d’un corps surdimensionné puis transmet le reste, l’application peut recevoir une partie que le WAF n’a jamais inspectée. Si elle rejette systématiquement, elle peut casser des téléversements légitimes. Une limite doit donc être définie par route et testée contre les formats réellement acceptés.

Un WAF moderne doit versionner ces plafonds et les adapter aux formats réellement acceptés par chaque service. NyxR applique ce modèle par service ; un échec REQBODY_ERROR produit une réponse HTTP 400 Bad Request, donc le refus explicite d’un corps que le moteur n’a pas su parser.

Dans l’extrait de production que j’ai constitué, 12 transactions provenant de trois adresses ont déclenché le contrôle local d’erreur de parsing. Le volume est faible, mais sa signification est forte : NyxR a distingué « contenu non reconnu » de « contenu reconnu et jugé bénin ».

Corréler l’identité sans ralentir le data plane

La chaîne précédente suffit à décider une transaction, mais son adresse IP ne suffit pas à relier durablement plusieurs requêtes au même logiciel ou au même comportement. La corrélation doit donc enrichir l’identité avec des traits protocolaires et temporels sans transformer le hot path en dépendance analytique.

Empreinte et comportement

La Network Address Translation (NAT) fait partager une adresse IP publique à plusieurs clients ; les proxys et sorties cloud produisent un effet similaire. Une adresse IP reste donc un point réseau observé, pas une identité humaine.

Avant d’envoyer le premier octet HTTP, le client TLS émet un ClientHello. Ce message propose notamment les versions TLS, les suites cryptographiques, les algorithmes de signature, les groupes de clés et des extensions comme SNI ou Application-Layer Protocol Negotiation (ALPN), mécanisme qui sélectionne notamment HTTP/1.1 ou HTTP/2 ; le serveur choisit ensuite les paramètres compatibles dans sa réponse. La passerelle qui termine TLS voit donc une description structurée des capacités et des choix de la bibliothèque cliente avant de voir la requête applicative.

JA4 transforme cette description en une empreinte a_b_c documentée par le projet FoxIO. La section a, lisible, encode le type de transport, la version TLS, la présence d’un nom SNI, le nombre de suites et d’extensions, puis un résumé de l’ALPN. La section b contient les 12 premiers caractères hexadécimaux du hachage de la liste triée des suites cryptographiques. La section c fait de même pour les extensions triées, hors SNI et ALPN, complétées par les algorithmes de signature. Secure Hash Algorithm 256 bits (SHA-256) est ici une fonction de hachage qui réduit une liste de taille variable à un résumé déterministe ; JA4 n’en conserve que 12 caractères pour la compacité.

Le tri retire l’effet de l’ordre aléatoire des suites et extensions. Les valeurs Generate Random Extensions And Sustain Extensibility (GREASE), valeurs factices injectées pour empêcher les implémentations TLS de se figer sur une liste connue, sont ignorées. JA4 est donc plus stable qu’une concaténation brute, mais ce n’est ni une authentification ni un identifiant unique : une bibliothèque partagée produit la même empreinte sur de nombreux hôtes, une pile configurable peut imiter un navigateur, une mise à jour peut changer l’empreinte et une terminaison TLS amont peut masquer le ClientHello original.

Un WAF moderne peut compléter JA4 avec des traits HTTP afin de ne pas dépendre d’un identifiant unique. Dans NyxR, j’ai choisi un composite fondé sur l’ordre des en-têtes, la famille de User-Agent, la version HTTP, les traits manquants et l’ALPN. Une mesure de cohérence compare les déclarations du client à sa forme protocolaire. L’objectif n’est pas de prouver qu’un client est malveillant, mais de rendre coûteuse la rotation d’un seul attribut.

Schéma technique
UNE ADRESSE IP N'EST PAS UN CLIENT: La confiance utile vient de signaux partiellement indépendants et de leur stabilité temporelle.IDENTITÉ ADVERSE · MODÈLE MULTI-SIGNALUNE ADRESSE IP N'EST PAS UN CLIENTLa confiance utile vient de signaux partiellement indépendants et de leur stabilité temporelle.RÉSEAU / PROTOCOLECOMPORTEMENT APPLICATIFÉTAT DE POLITIQUEtraitshistoriqueRÉSEAUIP · ASNpays · feedTLSJA4traits ClientHelloCOMPOSITE HTTPOrdre headers · famille UAprotocole · headers absentsÉTATSessioncookie · challengeSÉQUENCERoutesdébit · erreursCOHÉRENCEDéclarations vs comportementforme navigateur · timingSCORE EXPLICABLEpreuves + décroissancepas une probabilitéMODE TESTJournalisersans bloquerAPPLIQUERFrictionlimite · défiDÉROGATIONallow · ban · expire · rollbackIP SOURCE · HOPS APPROUVÉS · TRAITS TLS · COHÉRENCE HTTP · SESSION · SÉQUENCE DE ROUTES · TEMPS
Graphe d'identité pour raisonner, pas pour attribuer une intention à partir d'un seul signal.Lecture du schémaLa réputation IP est rapide, mais fusionne les utilisateurs derrière NAT, proxies et sorties cloud. Les fingerprints TLS et HTTP décrivent des traits d'implémentation, pas une personne. L'état de session, la séquence des routes et les challenges ajoutent une preuve temporelle. Une politique moderne garde chaque contribution visible et peut calculer puis journaliser un fingerprint sans le laisser modifier la réponse avant son activation explicite.

Une empreinte stabilise le contexte technique du client, mais elle ne démontre aucune intention malveillante. Le score comportemental ajoute la dimension temporelle en accumulant seulement des événements explicables, puis en réduisant leur poids lorsqu’ils ne se répètent plus.

Le score comportemental serveur est cumulatif et décroît avec une demi-vie, durée au bout de laquelle le poids d’un signal est divisé par deux. Seuls les événements porteurs d’un signal positif touchent Redis. Une requête bénigne ne soustrait pas artificiellement des points ; le silence fait décroître le score. Les signaux ModSecurity et du journal d’accès sont disjoints afin de ne pas compter deux fois la même transaction.

ImportantUn score comportemental n'est pas une vérité

Le score est un accumulateur de preuves pondérées. Il peut devenir très élevé pendant une séquence d’automatisation soutenue. Il ne doit jamais être affiché comme un pourcentage de malveillance.

Le score applique des règles déterministes déjà publiées ; il ne découvre pas seul de nouveaux profils ni ne valide leur innocuité. L’apprentissage peut analyser l’historique pour proposer une évolution, à condition de rester asynchrone et de repasser par le control plane avant toute mise en application.

Apprendre hors du chemin critique

Un système adaptatif dangereux applique directement une sortie probabiliste sur chaque requête. Il ajoute une dépendance réseau, rend la latence non déterministe, expose le data plane aux quotas du fournisseur et transforme une indisponibilité du modèle en changement de sécurité.

Une architecture adaptative moderne doit séparer le moteur de scénarios local, qui reconnaît des séquences déterministes, le scorer asynchrone, qui agrège les événements après la requête, et le service de verdict, qui ajoute un avis de classification. J’ai matérialisé cette séparation dans NyxR : une règle apprise commence en mode d’observation, où son résultat est journalisé sans influencer la réponse, puis la nouvelle politique traverse la chaîne de construction et de validation avant de pouvoir devenir active.

Schéma technique
L'APPRENTISSAGE NE DOIT PAS POSSÉDER LE HOT PATH: Les preuves améliorent la politique future sans retarder ni corrompre la requête présente.BOUCLE DE CONTRÔLE · ADAPTATION SÛREL'APPRENTISSAGE NE DOIT PAS POSSÉDER LE HOT PATHLes preuves améliorent la politique future sans retarder ni corrompre la requête présente.BOUCLE FERMÉE AVEC GARDES EXPLICITESéchecpublier1 · EDGEDécisionsnapshot local2 · PREUVESÉvénementsaction · raison3 · ANALYSERScore +campagnes4 · PROPOSERProfil testnon actif5 · CONSTRUIREBundleversionné6 · VÉRIFIERnginx -t+ santé7 · ACTIVERBasculeatomiqueÉCHECRollbackbundle précédentCONTRÔLEOpérateurexpliquer · dérogerDÉCIDER LOCALEMENT · CORRÉLER EN ASYNCHRONE · TESTER AVANT DE BLOQUER · VALIDER LE BUNDLE · ROLLBACK ATOMIQUE
Boucle NyxR : décision synchrone à l'edge, corrélation asynchrone, publication versionnée.Lecture du schémaL'edge émet une preuve structurée après application de la politique courante. Le scorer corrèle un historique borné et le learner propose des profils ou campagnes. Les nouvelles règles commencent si possible en observation. Le config builder valide un bundle complet, le bascule atomiquement et teste la santé du gateway. Un échec restaure la version précédente.

Dans NyxR, j’ai conçu le service de verdict comme un enrichissement asynchrone qui ne possède jamais la décision courante. Les données de nyxr.app en fournissent un exemple concret : pendant les dernières 24 heures de la fenêtre, ce service a dépassé son délai de réponse, le scorer a journalisé le repli vers le seuil local et la passerelle est restée saine. Le fait utile n’est donc pas la disponibilité du modèle d’intelligence artificielle, mais la décision déterministe qui reste appliquée lorsqu’il ne répond pas.

Sur les 482 bans durables créés dans la fenêtre :

  • 275 proviennent du scorer comportemental et 207 de scénarios edge adoptés dans l’historique ;
  • ils concernent 316 cibles distinctes ;
  • le score médian au ban est 42, le 95e percentile 103 et le maximum 4 224 ;
  • l’enrichissement stocké conclut ban pour 434, monitor pour 46 et allow pour 2.

Ces deux verdicts allow ne rendent pas les autres décisions fausses. Ils prouvent qu’un classifieur, composant qui affecte une observation à une catégorie, et un seuil déterministe peuvent diverger. Le système doit alors montrer la priorité de politique, la durée de la dérogation, la décision finale et la possibilité d’annulation.

Un mois de trafic réel issu de nyxr.app

Les mécanismes précédents définissent ce que le système devrait séparer : représentation, identité, signal, décision et apprentissage. Les données de production permettent maintenant de vérifier si ces frontières restent visibles dans les événements, sans confondre plusieurs projections d’une même requête.

Lire les résultats sans double compter

J’ai extrait ces agrégats de mon application nyxr.app. Ils couvrent un mois d’activité, du 21 juin au 22 juillet 2026.

La table relationnelle security_events conserve deux projections, c’est-à-dire deux représentations du même trafic destinées à des usages distincts :

  • 145 996 requêtes journalisées par la passerelle avec leur résultat final ;
  • 20 878 transactions d’audit ModSecurity, qui portent le match de règle principal et le contexte WAF.
NoteDeux projections d'un même trafic

Le total physique est 166 874 enregistrements, mais il ne représente pas 166 874 requêtes uniques. Une même requête peut produire un enregistrement dans chaque projection.

Schéma technique
31 JOURS DE TRAFIC NYXR EN PRODUCTION: Les résultats de la passerelle et les audits ModSecurity sont comptés séparément.MÉTRIQUES PRODUCTION · 21 JUIN AU 22 JUILLET · HEURE DE PARIS (UTC+2)31 JOURS DE TRAFIC NYXR EN PRODUCTIONLes résultats de la passerelle et les audits ModSecurity sont comptés séparément.PROJECTION DES ACCÈS DE LA PASSERELLEPROJECTION WAF / COMPORTEMENTTRAFIC145,996résultats journalisésAUTORISER120,27882.38%BLOQUER17,32011.86%FRICTION8,398challenge · ban · limiteADRESSES8,638sources uniquesAUDIT MODSEC20,878transactions avec matchLFI / CHEMINS6,667203 adressesPROTOCOLE7,145288 adressesBANS AUTO482316 ciblesFINGERPRINT2,218journalisénon bloquantFENÊTRE FIGÉE · AGRÉGATS ASSAINIS · AUCUN PAYLOAD BRUT · AUCUNE IP UTILISATEUR · COMPTES PAR SOURCE
Métriques NyxR enregistrées jusqu'au 22 juillet 2026 à 11:47:17, heure de Paris (UTC+2).Lecture du schémaLa ligne supérieure décrit les requêtes journalisées par la passerelle, un enregistrement par résultat final. La ligne inférieure décrit les transactions d'audit ModSecurity et l'état appris. Ces projections peuvent représenter la même transaction : leurs comptes ne doivent jamais être additionnés puis appelés requêtes uniques.

Ce périmètre fixe les dénominateurs et évite d’additionner le journal de la passerelle à l’audit WAF. Une fois cette distinction établie, la projection du data plane permet de mesurer les actions effectivement renvoyées aux clients.

Résultats de la passerelle.

Action finaleRequêtes journaliséesPart des 145 996
Autorisée120 27882,38 %
Bloquée17 32011,86 %
Challenge présenté4 9093,36 %
Client déjà banni2 8371,94 %
Rate limited6520,45 %

Sur cette fenêtre, NyxR a journalisé 8 638 adresses réelles distinctes sur 23 services. Une adresse n’est pas un acteur et un service n’est pas une population homogène. Ces cardinalités servent à décrire la surface, pas à attribuer des attaques.

Les raisons explicites montrent plusieurs contrôles actifs : 4 212 blocages CTI sur 762 adresses, 3 952 blocages géographiques sur 1 716 adresses et 97 blocages issus d’un honeypot, service leurre conçu pour attirer et signaler les interactions suspectes, sur 74 adresses. Cependant, 8 994 enregistrements marqués blocked n’ont pas de block_reason spécifique, principalement dans les données antérieures à l’instrumentation complète. Ce trou interdit d’utiliser la colonne comme vérité exhaustive sur toute la fenêtre.

Les actions finales indiquent ce qui a été appliqué, pas quelle source de renseignement a contribué à un blocage CTI. La provenance relie chaque indicateur aux listes qui le portaient afin de comparer leur couverture réelle, leur fraîcheur et leur recouvrement.

Quelles listes CTI ont réellement déclenché ?

La provenance CTI associe l’indicateur qui a bloqué une requête aux listes publiques qui le contenaient. Elle distingue ainsi la taille d’un catalogue de son efficacité mesurée. Sur les 4 212 requêtes bloquées par CTI, 3 968 restent attribuables au snapshot de provenance courant ; 244 ne peuvent plus lui être rattachées, notamment parce qu’elles précèdent l’enregistrement systématique de la clé exacte ou concernent un indicateur sorti depuis d’une liste rotative.

Source et logique de constructionBlocages attribuésAdresses source distinctesIndicateurs dans le snapshot actif
Data-Shield IPv4 Blocklist, variante Critical, registre issu de sondes mondiales avec une rétention glissante annoncée de 15 jours3 731, soit 88,6 % des blocages CTI55878 699
Blocklist.de, liste « all attacks », adresses signalées pour des attaques contre les serveurs participants pendant les 48 dernières heures946, soit 22,5 %9128 795
IPsum niveau 3+, agrégat quotidien qui ne conserve ici que les adresses présentes dans au moins trois listes publiques811, soit 19,3 %11718 178

Ces comptes ne sont pas exclusifs : une requête est attribuée à chaque liste qui porte l’indicateur correspondant. Les trois lignes totalisent donc 5 488 attributions pour 4 212 blocages uniques. Ce chevauchement est informatif. Data-Shield apporte la couverture la plus large et domine le trafic arrêté ; Blocklist.de privilégie une fenêtre très fraîche ; IPsum apporte un critère de consensus inter-listes. Le retour opérationnel est de conserver cette provenance par indicateur, d’expirer les sources selon leur propre cadence et de mesurer les faux positifs par liste. Additionner les compteurs ou juger une source sur sa seule taille produirait une conclusion erronée.

La CTI explique des décisions prises à partir d’indicateurs déjà connus ; elle ne décrit pas les séquences reconnues après plusieurs événements. Les dossiers de bans comportementaux montrent quels signaux temporels ou applicatifs ont réellement contribué à cette autre voie de décision.

Quels signaux comportementaux ont le plus contribué ?

Le tableau suivant porte sur les preuves enregistrées dans les 482 bans durables, pas sur des seuils privés de déploiement ni sur toutes les requêtes brutes. Un même ban peut contenir plusieurs signaux ; « bans contenant le signal » compte chaque ban une seule fois, tandis que « occurrences » conserve les répétitions présentes dans son dossier de preuves.

Famille de signalMécanisme enregistréBans contenant le signalOccurrences de preuve
Sonde de chemin sensibleLa route demandée ressemble à une recherche de métadonnées de gestion de versions, de fichiers de configuration, de sauvegardes ou d’interfaces d’administration. Le signal décrit une catégorie ; il ne révèle pas la liste exacte de chemins du déploiement.205, soit 42,5 %207
LFI / RFILes familles CRS 930 et CRS 931 ont reconnu une forme d’inclusion de fichier local ou distant. C’est la preuve d’un motif d’attaque, pas celle qu’un fichier a été lu.106, soit 22,0 %106
Anomalie de protocole HTTPLes familles CRS 920 et CRS 921 signalent un cadrage, une méthode ou des en-têtes incohérents. Ce signal est utile contre les parseurs divergents, mais doit être testé avec les clients et proxies légitimes.78, soit 16,2 %86

Au total, 387 bans sur 482, soit 80,3 %, contiennent au moins une de ces trois familles. Deux seulement en combinent deux dans le même dossier de preuves. Le fait dominant n’est donc pas une exploitation applicative confirmée : c’est une reconnaissance automatisée centrée sur les chemins, les inclusions de fichiers et les écarts de protocole. Le meilleur gain défensif consiste à arrêter ces séquences localement, puis à conserver les matches WAF et le contexte temporel pour expliquer le ban.

Les signaux de ban décrivent une accumulation dans le temps, tandis que l’audit ModSecurity conserve la vue transactionnelle du moteur de règles. Classer son identifiant principal permet de comparer les familles détectées, mais pas de transformer chaque correspondance en blocage effectif.

Familles de règles enregistrées.

Le tableau classe chaque transaction ModSecurity par son rule_id principal, identifiant numérique de la règle retenue comme cause principale. Il ne recompte pas les règles secondaires présentes dans le tableau d’audit.

Famille principaleTransactions d’auditAdresses distinctesAction WAF blockedScore max
Protocole HTTP, policy et cadrage, règles 920 à 9227 1452883 371970
LFI, chemins et fichiers restreints, règles 9306 6672035 54530
Détection de scanners, règles 9133 39733 3975
SQLi, règles 9421 558553820
RCE et injection de commandes, règles 9321 454796155
Fuite d’information en réponse, règles 950 à 954466155404
XSS, règles 941110142925
Injection PHP, langage exécuté côté serveur, règles 93334605
Remote File Inclusion (RFI), inclusion de fichier distant, règles 9312011720
Erreur de parsing de corps, contrôle local12355

Le contraste entre 1 558 transactions SQLi et seulement 38 actions WAF blocked montre pourquoi « match » ne signifie pas automatiquement « requête refusée ». Certaines transactions sont en détection, d’autres peuvent avoir un score sous le seuil ou provenir d’une politique différente. L’analyse correcte nécessite le mode et la version de configuration.

Ces agrégats révèlent les familles dominantes, mais effacent l’ordre et la cadence des requêtes. Une fenêtre de campagne restitue cette dimension temporelle, puis permet de confronter l’identité calculée et le coût de la décision au comportement réellement observé.

Campagne, empreintes et latence

Cas réel : une campagne de fuzzing d’URI.

Schéma technique
UN SCANNER, 2 100 CHEMINS DISTINCTS: L'edge a absorbé une campagne concentrée de fuzzing d'URI sans attendre l'analyse backend.CAS TERRAIN · 23 JUIN 2026 · 19:35 À 19:40, HEURE DE PARIS (UTC+2)UN SCANNER, 2 100 CHEMINS DISTINCTSL'edge a absorbé une campagne concentrée de fuzzing d'URI sans attendre l'analyse backend.CAMPAGNE DE FUZZING D'URIRÉSULTAT MESURÉmesurerSOURCE1 scannerUA de scannerFUZZING D'URI3 119requêtesfenêtre de 5 minutesLARGEUR2 100 URIchemins distinctsCRS 9131003 121 matchesd'auditdétection scannerDÉCISION3 117bloquées2 autoriséesDÉBIT10,4 req/s en moyenneLATENCE LOCALEp50 0 ms · p95 0 msPIRE ÉCHANTILLON477 msORIGINEnon jointe5 MINUTES · 10,4 REQUÊTES/S EN MOYENNE · 99,94 % BLOQUÉES · P95 0 MS · ORIGINE ÉPARGNÉE
Reconstruction assainie depuis les requêtes journalisées par la passerelle et les audits de la règle ModSecurity 913100.Lecture du schémaLa passerelle a journalisé 3 119 requêtes en cinq minutes : 3 117 ont été bloquées et deux autorisées. Le scanner a parcouru 2 100 URI distinctes. ModSecurity a émis séparément 3 121 matches pour la règle CRS 913100. Comme le refus local n'a pas joint l'origine, la latence mesurée à la passerelle est de 0 ms aux p50 et p95 ; le maximum atteint 477 ms.

Cette campagne de fuzzing d’URI, génération automatisée de nombreux chemins pour découvrir des routes, fichiers ou comportements inattendus, a produit 3 119 requêtes journalisées par la passerelle en 300 secondes, soit 10,4 requêtes par seconde en moyenne. Il ne s’agit pas d’un Distributed Denial of Service (DDoS) volumétrique, attaque distribuée qui cherche à épuiser une ressource par le volume, mais d’une exploration rapide et large de chemins applicatifs avec une signature de scanner et 2 100 URI distinctes.

Les 3 117 refus locaux ont un p50 et un p95 de 0 ms dans le journal NGINX. Le maximum de la fenêtre est 477 ms. L’intérêt n’est pas de revendiquer « zéro latence WAF », car la résolution de l’horloge et le chemin de journalisation limitent cette mesure. L’intérêt est que la décision n’attend pas l’origine ni un moteur distant.

Cette campagne enseigne aussi le risque du double comptage : les 3 121 audits de règle 913100 et les 3 119 requêtes journalisées par la passerelle décrivent deux projections presque jumelles, pas 6 240 requêtes.

La séquence et la signature du scanner caractérisent cette campagne, mais elles n’établissent pas une identité client durable. Les empreintes permettent d’étudier la continuité technique entre plusieurs adresses, à condition de conserver leurs limites et leur rôle expérimental.

Empreintes mesurées et limite de JA4.

65 446 requêtes journalisées possèdent un fingerprint composite non vide, soit 44,83 % des accès de la passerelle. Elles couvrent 935 fingerprints et 6 104 adresses journalisées. La cohérence vaut 0 sur 9 047 enregistrements et 1 sur 56 399.

224 fingerprints apparaissent derrière au moins cinq adresses. Parmi eux, 67 ont une proportion d’actions non autorisées supérieure ou égale à 50 %. Le fingerprint le plus distribué apparaît derrière 1 235 adresses. Cela ne prouve pas l’existence d’un botnet : une pile commune, un CDN ou un navigateur populaire peut produire une forte dispersion.

Le learner, composant qui regroupe les observations récurrentes pour proposer des profils, a créé 24 profils classés distributed_attack et 12 campagnes actives représentant 1 071 requêtes et 277 appartenances d’IP cumulées. Ces comptes sont des hypothèses de clustering, regroupement algorithmique d’observations similaires, pas des identités d’attaquants.

WarningAucune valeur JA4 non vide

Le résultat le plus important est négatif : aucune valeur JA4 non vide n’est présente dans cette fenêtre, malgré le champ et le code d’instrumentation. Je ne peux donc pas attribuer les décisions de cette fenêtre à JA4. La terminaison TLS, la version du runtime, c’est-à-dire du logiciel réellement exécuté, et la chaîne d’ingestion, parcours qui transporte le signal jusqu’au stockage, doivent être vérifiées avant d’évaluer l’efficacité de ce signal.

Enfin, 2 218 requêtes journalisées ont block_reason=fingerprint alors que leur action finale est allowed. Le fingerprint a donc été calculé et journalisé comme signal expérimental, mais il n’a pas participé au blocage de ces requêtes. Cette distinction évite qu’un tableau de bord transforme une expérimentation en protection active imaginaire.

La couverture des empreintes indique quels signaux étaient disponibles, pas le coût de leur évaluation ni celui des autres contrôles. Les percentiles par action séparent donc la réponse locale du temps passé auprès de l’origine avant toute conclusion sur la performance du WAF.

Coût et latence.

Les percentiles calculés sur les requêtes journalisées par la passerelle séparent les actions. Le p99 est la valeur sous laquelle se trouvent 99 % des mesures, et expose une traîne de latence que la médiane masque.

Actionp50p95p99
Autorisée71 ms707 ms2 641,7 ms
Bloquée localement0 ms0 ms49 ms
Challenge4 ms42 ms63 ms
Client déjà banni0 ms0 ms2 ms
Rate limited2 ms11 ms325,5 ms

La latence autorisée inclut le temps de l’origine ; elle ne mesure pas le coût du WAF seul. La latence bloquée mesure surtout la production locale de la réponse. Un benchmark est une mesure comparative réalisée sous des conditions contrôlées : il doit ici comparer un même endpoint, un même payload et une même origine avec WAF désactivé, DetectionOnly puis On, tout en séparant p50, p95, p99, temps processeur, allocations mémoire, taille du corps et nombre de règles évaluées.

Ce que le WAF peut et ne peut pas garantir

Les métriques précédentes mesurent des décisions et des signaux observables ; elles n’étendent pas pour autant les connaissances du WAF sur l’état interne de l’application. Définir cette limite évite de présenter une détection syntaxique ou comportementale comme une preuve d’exploitation ou d’autorisation correcte.

Un WAF peut très bien reconnaître une tentative de path traversal sans savoir si le fichier visé existe. Il peut détecter une forme SQLi sans connaître la requête SQL finalement construite. Il peut limiter un flux d’achat sans savoir si l’utilisateur possède légitimement cent comptes. Il peut observer un identifiant d’objet sans savoir si le principal authentifié a le droit métier de le lire.

Une Application Programming Interface (API) expose des opérations et des données applicatives à d’autres logiciels au moyen d’un contrat. L’OWASP API Security Top 10 2023 place les défauts d’autorisation sur les objets et les fonctions parmi les risques majeurs. Un WAF générique ne dispose pas du graphe d’autorisations de l’application : celle-ci doit encore vérifier l’identité authentifiée, l’objet, l’action et le contexte.

Classe de risqueWAF utileContrôle propriétaire réel
Injection syntaxiqueParser, transformations, CRS, schéma de contenuRequêtes paramétrées, encodage de sortie, validation typée
Broken Object Level Authorization (BOLA), aussi appelé Insecure Direct Object Reference (IDOR) : accès à l’objet d’un autre utilisateurAnomalies de route et de volumeAutorisation objet dans l’application
Autorisation de fonction cassée : appel d’une opération interdite au rôle courantProtection de routes connuesRole-Based Access Control (RBAC), droits par rôle, ou Attribute-Based Access Control (ABAC), droits calculés depuis des attributs, côté service
Abus de workflowRate limit, session, challenge, séquenceInvariants métier et contrôles transactionnels
Server-Side Request Forgery (SSRF) : faire émettre au serveur une requête vers une destination choisieDétection de formes et allowlist d’URLRésolveur, politique réseau sortante et validation après Domain Name System (DNS), service qui traduit un nom en adresse IP
Request smugglingParsing strict et normalisation edgeAccord de cadrage sur toute la chaîne
Compromission de l’origineBlocage du trafic directPare-feu, mTLS, secret d’origine, segmentation

Le WAF est donc un contrôle compensatoire, c’est-à-dire une couche qui réduit un risque sans corriger sa cause dans l’application, et un point d’observation puissant. Il n’est pas un substitut au code sûr.

Évaluer un WAF comme un red teamer

Les limites de responsabilité précédentes déterminent la méthode de test : l’évaluation doit comparer les représentations vues par la périphérie, le WAF et l’origine, puis attribuer chaque résultat au composant qui possède réellement le contrôle.

Une red team simule un attaquant dans un périmètre autorisé afin d’évaluer les contrôles et leur détection. Son évaluation ne demande pas seulement « quel payload passe ? » : elle cherche quelle représentation voit chaque composant, quelle décision a été prise et quelle preuve reste après la tentative.

Protocole d’évaluation reproductible

1. Cartographier la chaîne réelle.

Relever :

  • la résolution DNS et les CDN éventuels ;
  • les protocoles acceptés côté client et côté origine ;
  • les downgrades HTTP/2 ou HTTP/3 vers HTTP/1.1 ;
  • les headers de forwarding et les CIDR de confiance ;
  • la possibilité de joindre l’origine directement ;
  • la version du moteur, de CRS et de la politique par hôte virtuel (vhost), configuration qui associe un nom de domaine à un service.

Une réponse différente sur l’IP d’origine et sur le domaine public est déjà une conclusion d’architecture.

La cartographie identifie les interprètes et les frontières de confiance, mais elle ne prouve pas qu’ils donnent le même sens aux mêmes octets. Les tests de différentiels doivent donc faire varier une seule dimension de représentation à la fois et comparer les sorties de chaque couche.

2. Tester les différentiels de parseur.

Faire varier un axe à la fois :

  • duplication de Content-Length, en-tête qui annonce la taille du corps, combinaison avec Transfer-Encoding, qui décrit son codage de transfert, et espaces non canoniques ;
  • HTTP/2 vers HTTP/1.1, pseudo-en-têtes HTTP/2 préfixés par : et normalisation des noms ;
  • paramètres dupliqués, tableaux implicites et encodages répétés ;
  • application/json, application/x-www-form-urlencoded, qui encode un formulaire en paires clé-valeur, multipart et XML ;
  • profondeur, nombre d’arguments, fichiers et corps partiels ;
  • URL absolue, dot segments, slashs encodés et séparateurs propres au framework.

Le résultat attendu n’est pas « alerte ». Il faut comparer ce que le WAF journalise à ce que l’application reçoit dans un lab instrumenté. Un fuzzer différentiel génère automatiquement des variantes d’entrée et cherche celles que deux parseurs interprètent différemment ; il est plus instructif qu’une longue liste statique de payloads.

Un différentiel établit quelles représentations divergent, mais pas si une correspondance de règle a effectivement modifié la requête. La preuve doit ensuite séparer le parsing, la détection, le calcul du score, l’action appliquée et ce que l’origine a reçu.

3. Séparer détection, score et action.

Pour chaque test, enregistrer :

DimensionPreuve minimale
ParsingProcesseur sélectionné, erreur éventuelle, variables produites
DétectionTous les rule IDs, targets, transformations et messages
ScoringContributions par PL, score total et seuil
EnforcementStatut local, connexion fermée ou requête transmise
OrigineRequête réellement reçue et réponse produite
ObservabilitéAccess log, audit WAF, corrélation et version de config

Cette chaîne de preuve relie le contenu à l’action ; elle reste invalide si les compteurs et réputations utilisent une identité contrôlable par l’attaquant. Il faut donc tester séparément la reconstruction de l’adresse, les empreintes et les collisions entre plusieurs clients légitimes.

4. Tester l’identité comme une frontière de sécurité.

Envoyer des en-têtes de forwarding depuis une source non approuvée, traverser chaque proxy légitime, comparer IPv4 et IPv6, puis vérifier la clé réellement utilisée pour CTI, rate limiting et bans. Tester la rotation d’IP avec un fingerprint stable et la stabilité d’IP avec plusieurs clients légitimes.

Un bon résultat peut être « le système refuse d’attribuer ». L’incertitude explicite est préférable à une fausse précision.

Les tests d’identité couvrent la relation entre clients et clés de décision, mais une automatisation peut répartir son activité sous chaque seuil instantané. L’étape suivante étend l’analyse aux fenêtres temporelles, à la cardinalité des états et aux séquences distribuées.

5. Tester le temps et la distribution.

Les signatures voient une transaction. Les abus modernes se répartissent :

  • faible débit sur de nombreuses IP ;
  • rotation d’User-Agent avec un même comportement de routes ;
  • endpoints coûteux appelés sous le seuil global ;
  • échec de challenge distribué ;
  • séquences rares qui restent bénignes isolément.

Mesurer les fenêtres, la demi-vie, la cardinalité des clés, la mémoire Redis, les collisions NAT et la récupération après expiration.

Un contrôle peut rester exact sous une charge nominale et changer silencieusement de politique lorsqu’une dépendance tombe. Les essais de panne vérifient donc quels mécanismes restent locaux, lesquels passent en observation et quel mode dégradé devient réellement actif.

6. Tester les modes de panne.

Couper en environnement autorisé et contrôlé : Redis, le scorer, le learner, le service de verdict, PostgreSQL et le control plane. Pour chaque panne, documenter ce qui continue, ce qui passe en observation, ce qui autorise par défaut, ce qui refuse par défaut et ce que voit l’opérateur.

Une panne analytique ne doit pas modifier le parsing. Une panne Redis ne doit pas désactiver silencieusement toute limite. Un bundle invalide ne doit jamais devenir actif. Une file d’événements pleine ne doit pas bloquer les workers HTTP, processus qui exécutent le traitement des requêtes.

Les modes de panne établissent la résilience, mais pas la spécificité d’une détection. Chaque cas malveillant doit donc être rejoué avec un voisin bénin de même forme pour déterminer quelle propriété déclenche réellement le résultat.

7. Rejouer avec des contrôles négatifs.

Chaque payload malveillant doit avoir un voisin bénin de même forme : même Content-Type, en-tête qui déclare le format du corps, même taille, même nombre de paramètres et même endpoint. Ce voisin est un contrôle négatif : il ne contient pas la propriété malveillante et vérifie que cette propriété, plutôt que la forme générale, déclenche le résultat.

Ces cas contrôlés ne deviennent comparables que si le lab conserve la configuration, les entrées et les sorties de chaque exécution. Une matrice reproductible transforme alors un contournement ponctuel en expérience vérifiable et falsifiable.

Construire un lab reproductible.

Le test suivant illustre une matrice minimale sans fournir de payload d’exploitation. Ce script de shell utilise curl, client en ligne de commande pour les protocoles réseau, afin d’envoyer le même document JSON à un endpoint de lab, comparer les modes du WAF et conserver les en-têtes de réponse.

waf-matrix.sh
set -euo pipefail
target="https://lab.example.test/api/parse"
artifact_dir="./artifacts/waf-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$artifact_dir"
for case_name in baseline duplicate-key deep-json oversized-fields; do
curl --http2 --silent --show-error \
--dump-header "$artifact_dir/$case_name.headers" \
--output "$artifact_dir/$case_name.body" \
--header 'Content-Type: application/json' \
--data-binary "@$case_name.json" \
--write-out '%{http_code} %{time_total}\n' \
"$target" | tee "$artifact_dir/$case_name.result"
done
sha256sum "$artifact_dir"/* > "$artifact_dir/SHA256SUMS"

sha256sum calcule ici une empreinte SHA-256 de chaque artefact ; toute modification ultérieure change cette valeur et devient détectable. La même exécution doit capturer côté serveur : version du bundle, mode WAF, PL, seuil, audit complet, journal de passerelle, requête normalisée reçue par l’origine et ressources consommées. Le test devient alors réfutable et reproductible.

Le lab produit des preuves détaillées pour chaque couche. Ces observations peuvent ensuite être condensées en invariants d’architecture qui restent valables lorsque le payload, l’adresse ou la signature de l’outil change.

Dix invariants qui résistent à l’évasion.

  1. Parser avant de classifier. Un contenu que le WAF ne peut pas parser doit suivre une politique explicite.
  2. Réduire les interprètes. Normaliser tôt, éviter les downgrades inutiles et aligner edge et origine.
  3. Établir l’identité avant les compteurs. Une clé de rate limit basée sur une IP spoofable n’est pas un contrôle.
  4. Garder les décisions locales. Le hot path ne doit pas attendre une base ou un modèle distant.
  5. Séparer signal, raison et action. Une décision calculée uniquement pour observation ne doit pas apparaître comme un blocage effectif.
  6. Versionner la politique. Une alerte sans version de configuration est difficile à reproduire.
  7. Préférer les exclusions étroites. L’objectif est de retirer un faux positif, pas une famille de protection.
  8. Borner tous les états. Corps, profondeur, arguments, cardinalités, files, historique et Time To Live (TTL), durée après laquelle un état expire.
  9. Mesurer les faux négatifs et les faux positifs. Un faux négatif est une attaque non détectée ; un taux de blocage seul récompense les systèmes inutilisables.
  10. Traiter le WAF comme une couche. L’autorisation et les invariants métier restent dans l’application.

Le modèle mental à retenir

Le protocole précédent transforme des tests isolés en propriétés durables. Pris ensemble, ses invariants conduisent à considérer le WAF comme un système distribué qui ne voit qu’une représentation partielle de la requête et de son auteur.

Le WAF moderne se comprend comme un système distribué partiellement observable. Le data plane reçoit une représentation de la requête, pas l’intention de l’opérateur. Il établit une frontière HTTP, reconstruit une identité probabiliste, applique une politique locale et émet une preuve. D’autres composants corrèlent cette preuve dans le temps, mais ne doivent pas réécrire rétroactivement ce qui s’est passé.

Pour analyser une évasion, quatre questions suffisent souvent :

  1. Quelle représentation exacte le WAF a-t-il parsée ?
  2. Quelle représentation l’origine a-t-elle finalement consommée ?
  3. Quel signal a contribué à quelle décision, sous quelle version de politique ?
  4. Quelles couches indépendantes restent actives si ce signal disparaît ?

Les résultats de production que je présente à partir de nyxr.app rendent ce modèle concret. NyxR a absorbé localement une campagne de fuzzing d’URI, calculé et journalisé un fingerprint sans l’utiliser pour bloquer, maintenu la passerelle lorsque le service de verdict a expiré et distingué une détection ModSecurity d’un blocage effectif. Ces faits rendent le système testable, ce qui est plus utile qu’une promesse de « protection intelligente ».

SummaryLa vision moderne

Un WAF efficace n’est pas celui qui bloque le plus. C’est celui qui interprète les requêtes avec le moins d’ambiguïté, décide avec un état local borné, explique chaque action, apprend sans posséder le hot path et reconnaît ce que seule l’application peut autoriser.

Références

Utiliser avec une IA

Actions