Écrire sa première règle Suricata : syntaxe, exemple CVE et test anti-faux-positifs
Comment écrire une règle Suricata : header, sticky buffers modernes, exemple CVE avant/après, test syntaxique sur pcap et piège anti-faux-positifs.
Un scanner passe sur le périmètre, une CVE tombe sur un composant exposé, ou l'équipe veut simplement détecter un comportement précis sur le trafic HTTP interne. Dans les trois cas, la réponse passe par la même compétence : écrire une règle Suricata. C'est la demande la plus fréquente côté détection réseau, et pourtant peu d'équipes maîtrisent la syntaxe au-delà du copier-coller d'exemples trouvés en ligne.
Ce guide couvre la structure complète d'une règle, un exemple concret de détection de CVE dans l'URI avec les sticky buffers modernes, la méthode pour tester avant de déployer, et le piège numéro un qui remplit les journaux de faux positifs : l'oubli de flow:established.
L'anatomie d'une règle Suricata
Une règle Suricata tient sur une ligne, mais chaque mot-clé a un rôle précis. Voici le squelette de base pour une détection HTTP :
alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"Descriptif de la detection";
flow:established,to_server;
content:"motif recherche";
sid:9000001;
rev:1;
metadata:created_at 2026_07_17;
)Décomposons.
- Le header (
alert http $EXTERNAL_NET any -> $HOME_NET any) fixe l'action (alert), le protocole applicatif (http, déjà parsé par Suricata), et la direction : trafic venant de l'extérieur vers le réseau protégé. Inverser les variables ou utiliserany -> anyfait perdre toute la sémantique de direction, essentielle pour distinguer une requête d'attaque d'une réponse serveur. msgest le libellé humain qui apparaît dans les alertes. Il doit être descriptif et stable dans le temps : c'est sur ce texte que l'analyste va trier en SOC.flow:established,to_serverrestreint le match aux connexions TCP pleinement établies, dans le sens client vers serveur. C'est un mot-clé de contexte, pas de contenu, mais son absence est la source numéro un de faux positifs (voir plus bas).contentest le motif recherché. Utilisé seul et sur le payload brut, il fonctionne mais reste fragile et coûteux : c'est là qu'interviennent les sticky buffers modernes.sid(signature ID) etrev(révision) identifient la règle de façon unique. Les sid internes doivent utiliser une plage dédiée pour ne jamais entrer en collision avec les feeds publics (ET Open, par exemple, réserve les sid sous 2000000).metadataporte les informations de traçabilité : date de création, référence CVE, classification. Ce n'est pas un détail : c'est ce qui permet de trier, corréler et faire vieillir une base de règles.
Un cas concret : détecter un CVE dans l'URI
Prenons un cas illustratif : une CVE affectant un portail web, exploitable via un paramètre cmd= transmis sur un endpoint précis, /api/v1/import. (L'exemple est illustratif : le principe compte plus que la CVE choisie.)
Avant : la version naïve, qui matche n'importe quoi.
alert tcp any any -> any any (msg:"ACME Portal RCE Attempt"; content:"/api/v1/import"; content:"cmd="; sid:9000001; rev:1;)Cette règle a quatre défauts : elle ignore complètement la direction du trafic (any any -> any any), elle ne vérifie pas que la connexion est établie, elle scanne le flux TCP brut au lieu du buffer HTTP déjà parsé, et elle n'a ni fast_pattern ni metadata.
Après : la version qui s'appuie sur les sticky buffers.
alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"EXPLOIT ACME Portal CVE-2024-0000 RCE Attempt via cmd Parameter";
flow:established,to_server;
http.method; content:"POST"; startswith;
http.uri; content:"/api/v1/import"; nocase; fast_pattern;
content:"cmd="; nocase; distance:0;
reference:cve,2024-0000;
classtype:attempted-admin;
metadata:created_at 2026_07_17, cve CVE_2024_0000;
sid:9000001;
rev:1;
)http.uri est un sticky buffer : il redirige les content suivants vers l'URI déjà normalisée et décodée par le moteur, plutôt que vers le paquet brut. C'est plus fiable (le décodage d'URL est fait une fois par Suricata, pas à réinventer dans la règle) et plus rapide (le moteur ne rescane pas tout le payload). Les autres sticky buffers courants suivent la même logique : tls.sni pour matcher un nom de domaine dans un ClientHello TLS sans avoir à déchiffrer le trafic, ou dns.query pour matcher une requête DNS précise, par exemple un domaine de commande et contrôle. Dans les trois cas, la règle raisonne sur un champ de protocole déjà interprété, pas sur des octets bruts.
Tester avant de déployer
Une règle qui n'a jamais tourné sur du trafic réel est une hypothèse, pas une détection. Deux étapes, dans l'ordre.
1. Vérifier la syntaxe.
suricata -T -S ma_regle.rules -c /etc/suricata/suricata.yamlLe flag -T lance un test de configuration, -S charge le fichier de règles à valider en plus du chargement normal. Cette étape attrape les erreurs de syntaxe (parenthèse manquante, mot-clé mal orthographié), mais ne dit rien sur l'efficacité de la règle.
2. Rejouer un pcap et vérifier le match.
suricata -r sample.pcap -S ma_regle.rules -k none -l /tmp/suricata-testIl faut ensuite ouvrir /tmp/suricata-test/fast.log ou eve.json et vérifier que l'alerte avec le sid attendu est bien présente, et surtout absente sur un pcap de trafic bénin ou d'une cible patchée. C'est cette double preuve, pas la seule validation syntaxique, qui distingue une règle fiable d'une règle qui « compile ».
Le piège numéro un : l'oubli de flow:established
C'est l'erreur la plus fréquente et la plus coûteuse en bruit. Sans flow:established,to_server, le moteur évalue la règle sur n'importe quel paquet correspondant au motif, y compris ceux d'un scan de ports qui envoie des payloads bruts sans jamais compléter la poignée de main TCP, ou des paquets malformés hors session. Résultat : la règle déclenche sur des scans (nmap, masscan) qui ne représentent aucune tentative d'exploitation réelle, et noie l'équipe dans des alertes sans valeur.
Deux compléments réduisent encore le risque de faux positif :
- Ancrer avec
http.method:http.method; content:"POST"; startswith;garantit que seule une requête POST déclenche l'alerte, cohérent avec le mode d'exploitation réel décrit dans l'avis de sécurité. - Gérer la casse avec
nocase: un attaquant qui testeCMD=au lieu decmd=pour contourner une règle sensible à la casse passe entre les mailles sinocaseest absent. À l'inverse, l'ajouter partout sans réflexion peut élargir le matching au-delà du nécessaire : à appliquer sur les motifs où la variation de casse est plausible, pas par réflexe.
Performance : fast_pattern et choix du buffer
À fort débit, chaque règle mal construite pèse sur le moteur. Deux leviers.
fast_pattern indique au moteur quel motif utiliser dans le multi-pattern-matcher pour le premier passage de filtrage, avant même d'évaluer le reste de la règle. Le placer sur le motif le plus long et le plus distinctif (/api/v1/import, quinze caractères) plutôt que sur un motif court et commun (cmd=, quatre caractères) réduit drastiquement le nombre de paquets qui déclenchent une évaluation complète de la règle.
Le choix du buffer compte tout autant. Matcher sur http.uri plutôt que sur le contenu brut du paquet TCP évite au moteur de rescaner du texte déjà découpé et normalisé par le parseur HTTP. Sur un lien à plusieurs gigabits, la différence entre une règle qui utilise les buffers de protocole et une règle en content brut se traduit directement en charge CPU et en risque de perte de paquets (drop) sous pression.
Prioriser : toutes les CVE ne se valent pas
Écrire une règle a un coût, même modeste. Deux sources publiques et gratuites permettent d'arbitrer où le mettre :
- CISA KEV (Known Exploited Vulnerabilities) : le catalogue des failles activement exploitées. Une CVE qui y figure n'est plus une hypothèse, c'est une urgence opérationnelle.
- EPSS (Exploit Prediction Scoring System) : une probabilité d'exploitation à 30 jours, utile pour trancher entre deux CVE avant que l'une des deux ne soit exploitée.
Croiser « cette CVE est dans le KEV » avec « elle touche un actif exposé chez moi » donne une liste courte et actionnable : ce sont les règles à écrire en premier, pas les quarante mille CVE de l'année.
En résumé
Une règle Suricata fiable repose sur une structure disciplinée (header, flow:established, sticky buffers, metadata), sur un principe de preuve empirique (syntaxe validée, puis match confirmé sur pcap vulnérable et silence sur cible patchée), et sur une priorisation par KEV et EPSS plutôt que par ordre d'arrivée des CVE.
Maintenir une base de règles NIDS à jour, testées et à faible taux de faux positifs demande du temps que peu d'équipes ont en interne. C'est exactement ce que couvre le pack de règles NIDS de ThreatClaw : des règles validées sur le moteur réel, priorisées par exploitation active, livrées prêtes à l'emploi plutôt qu'à écrire sous pression un vendredi soir.
Articles liés
ET Open est gratuit, ET Pro et Talos sont payants, et les feeds curés se situent entre les deux. Comparaison pratique des rulesets Suricata et Snort pour la détection réseau, avec les critères qui comptent pour un SOC ou un MSSP.
JA3 faiblit face à TLS 1.3. Configurez JA4 dans Suricata, écrivez une règle ja4.hash et corrélez avec le SNI pour détecter le C2 chiffré caché.
Suricata vs Snort : comparatif architecture, compatibilité des règles, couverture ICS/OT et migration, pour arbitrer son moteur NIDS en connaissance de cause.
Détection intrusion réseau NIDS : où Suricata voit ce que l'EDR ne voit pas, anatomie d'une règle, et pourquoi un pack de règles curées bat un flux brut.