Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 718 minIntermédiaire

Écrire une règle Wazuh personnalisée et un décodeur

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • tester une ligne de journal avec wazuh-logtest ;
  • écrire une règle personnalisée accrochée à une règle existante ;
  • écrire un décodeur pour le journal d'une application ;
  • réduire un faux positif sans aveugler la détection ;
  • changer le niveau d'une règle fournie avec overwrite.

3 exercices corrigés

Une règle Wazuh personnalisée est une règle que tu écris dans /var/ossec/etc/rules/local_rules.xml pour adapter la détection à ton organisation : relever le niveau de ce qui compte chez toi, repérer un enchaînement propre à ton infrastructure, écarter un faux positif précis. Quand le journal vient d'une application que Wazuh ne connaît pas, tu écris d'abord un décodeur dans local_decoder.xml. Ce chapitre apprend à tester avec wazuh-logtest, puis à écrire une règle, un décodeur et une exception, en évitant les erreurs classiques. Il suppose que tu sais lire une règle et une alerte (chapitre 6).

Tester avec wazuh-logtest

Avant de toucher à une règle, on teste. L'outil wazuh-logtest rejoue une ligne de journal dans le moteur d'analyse et montre les trois phases :

bash
sudo /var/ossec/bin/wazuh-logtest

Colle la ligne, valide : l'outil affiche la phase 1 (horodatage, machine, programme), la phase 2 (nom du décodeur et champs extraits), la phase 3 (identifiant, niveau, description et groupes de la règle retenue), puis indique si une alerte serait générée. Pour tester une règle de fréquence, colle plusieurs lignes à la suite dans la même session : l'outil garde la mémoire des événements précédents. Le tableau de bord offre la même chose dans Server management > Ruleset Test.

wazuh-logtest lit les fichiers de règles à chaque session : une règle que tu viens d'enregistrer se teste tout de suite, sans redémarrer. Le serveur, lui, ne l'appliquera aux vrais événements qu'après un redémarrage de wazuh-manager.

Écrire une règle personnalisée

Tes règles vont dans /var/ossec/etc/rules/local_rules.xml (ou dans un autre fichier du même dossier), jamais dans /var/ossec/ruleset/rules/, que chaque mise à jour remplace. Trois règles du jeu :

  • des identifiants entre 100000 et 120000, réservés aux règles locales ;
  • chaque règle dans un bloc <group name="…,"> (la virgule finale est d'usage) ;
  • une règle personnalisée s'accroche de préférence à une règle existante (if_sid), qui a déjà fait le travail de décodage et de tri.

Exemple 1 : un échec SSH sur root qui ne vient pas du VLAN d'administration. Chez Sorbier, seul le VLAN 99 a le droit d'administrer srv-web en SSH. Une tentative sur root venue d'ailleurs est donc anormale, même isolée : elle mérite plus que le niveau 5 de la règle 5760.

xml
<group name="sorbier,sshd,">
  <rule id="100100" level="10">
    <if_sid>5760</if_sid>
    <user>^root$</user>
    <srcip negate="yes">192.168.10.208/28</srcip>
    <description>Sorbier : échec SSH sur root hors du VLAN d'administration</description>
    <mitre>
      <id>T1110.001</id>
    </mitre>
    <group>authentication_failed,</group>
  </rule>
</group>

La règle ne s'évalue que si 5760 a correspondu, et seulement si l'utilisateur décodé est exactement root et que l'adresse source n'est pas dans 192.168.10.208/28. Pour la ligne suivie au chapitre 6 (Failed password for root from 192.168.10.71), elle devient la règle la plus précise : l'alerte est de niveau 10. Garder le groupe authentication_failed permet aux règles de corrélation qui comptent ce groupe de continuer à la compter.

Exemple 2 : un groupe d'administration modifié hors des heures ouvrées. L'ajout d'un compte au groupe local Administrateurs (règle 60154) ou au groupe Admins du domaine (règle 60159) est déjà de niveau 12. La nuit, chez Sorbier, personne n'administre l'annuaire : c'est presque sûrement une attaque.

xml
<group name="sorbier,windows,">
  <rule id="100110" level="14">
    <if_sid>60154,60159</if_sid>
    <time>7 pm - 7 am</time>
    <description>Sorbier : groupe d'administration modifié en dehors des heures ouvrées</description>
    <mitre>
      <id>T1098</id>
    </mitre>
    <group>group_changed,</group>
  </rule>
</group>

<time> compare l'heure du serveur Wazuh, pas l'horodatage de l'événement : une raison de plus pour que toutes les machines soient à l'heure et dans le même fuseau. Une seconde règle avec <weekday>weekends</weekday> couvrirait les samedis et dimanches.

Enregistre, teste avec wazuh-logtest quand c'est possible, puis applique :

bash
sudo systemctl restart wazuh-manager
sudo tail -n 20 /var/ossec/logs/ossec.log   # aucune erreur de chargement des règles ?

Si le XML est invalide (balise non fermée), le serveur refuse de démarrer et ossec.log donne le fichier et la ligne : corrige avant tout. Un identifiant en double ne bloque pas le démarrage : Wazuh écrit un avertissement (« Rule ID … is duplicated ») et ignore la seconde règle, sans autre signe. Le tableau de bord permet aussi d'éditer local_rules.xml depuis Server management > Rules, dans la liste des fichiers personnalisés.

Écrire un décodeur

Une règle ne peut travailler que sur des champs décodés. Pour un journal que Wazuh ne connaît pas, il faut d'abord un décodeur. Le site de suivi des colis, sur srv-web, écrit ses événements de connexion à l'espace client dans journald :

text
Nov  3 09:14:22 srv-web suivi-colis[812]: LOGIN_FAILED user=client4471 ip=198.51.100.23

Dans /var/ossec/etc/decoders/local_decoder.xml :

xml
<decoder name="suivi-colis">
  <program_name>^suivi-colis$</program_name>
</decoder>

<decoder name="suivi-colis-champs">
  <parent>suivi-colis</parent>
  <regex>^(\w+) user=(\S+) ip=(\S+)</regex>
  <order>action, dstuser, srcip</order>
</decoder>

Le décodeur parent s'applique aux lignes dont le programme s'appelle exactement suivi-colis. L'enfant découpe le message : (\w+) capture un mot (LOGIN_FAILED), puis (\S+) une suite de caractères sans espace, deux fois. <order> nomme les captures dans l'ordre, avec les noms de champs standard de Wazuh (action, dstuser, srcip), que les règles savent tester directement. La syntaxe par défaut des expressions est celle de Wazuh (OS_Regex), plus limitée que PCRE : \w, \d, \s, \S, +, *, ^, $ et les parenthèses suffisent pour l'essentiel ; ajoute type="pcre2" pour une expression plus riche.

Puis, dans local_rules.xml, les règles qui s'en servent :

xml
<group name="sorbier,suivi-colis,">
  <rule id="100120" level="0">
    <decoded_as>suivi-colis</decoded_as>
    <description>Suivi des colis : événements de l'application</description>
  </rule>

  <rule id="100121" level="5">
    <if_sid>100120</if_sid>
    <action>LOGIN_FAILED</action>
    <description>Suivi des colis : échec de connexion à l'espace client</description>
    <group>authentication_failed,</group>
  </rule>

  <rule id="100122" level="10" frequency="10" timeframe="120">
    <if_matched_sid>100121</if_matched_sid>
    <same_srcip />
    <description>Suivi des colis : échecs de connexion répétés depuis la même adresse</description>
    <mitre>
      <id>T1110</id>
    </mitre>
    <group>authentication_failures,</group>
  </rule>
</group>

La règle 100120, de niveau 0, regroupe tout ce que produit l'application sans alerter : c'est le parent auquel s'accrochent les autres, comme la règle 5700 pour SSH. Si les développeurs peuvent écrire leurs journaux en JSON, tout cela devient inutile : le décodeur JSON intégré extrait chaque clé, et les règles testent directement <field name="evenement">.

Réduire un faux positif

Le copieur multifonction de la logistique (192.168.10.90) dépose les numérisations sur srv-fichiers avec le compte de service svc-scan, dont le mot de passe a changé sans que le copieur soit mis à jour. Résultat : un 4625 toutes les cinq minutes, une alerte 60122 à chaque fois, et des alertes de force brute qui finissent par ne plus être lues.

La bonne réponse est d'abord de corriger la cause : mettre à jour le mot de passe dans le copieur. En attendant l'intervention, on écarte ce faux positif précis, et lui seul, avec une règle de niveau 0 accrochée à la règle bruyante :

xml
<group name="sorbier,faux-positifs,">
  <rule id="100130" level="0">
    <if_sid>60122</if_sid>
    <field name="win.eventdata.targetUserName" type="pcre2">(?i)^svc-scan$</field>
    <field name="win.eventdata.ipAddress" type="pcre2">^192\.168\.10\.90$</field>
    <description>Faux positif connu : copieur de la logistique, compte svc-scan (ticket 2026-1187)</description>
  </rule>
</group>

Les deux champs doivent correspondre : un échec de svc-scan depuis une autre adresse, ou un échec d'un autre compte depuis le copieur, continue de lever l'alerte. La description cite le ticket pour que l'exception soit retirée une fois la cause corrigée.

Changer le niveau d'une règle fournie. Pour qu'un journal Windows effacé (règle 60117, niveau 9) parte par courriel au-delà du seuil de 12, recopie la règle entière dans local_rules.xml, change son niveau et ajoute overwrite="yes" :

xml
<group name="sorbier,windows,">
  <rule id="60117" level="12" overwrite="yes">
    <if_sid>60100</if_sid>
    <field name="win.system.eventID">^1102$</field>
    <description>Windows audit log was cleared</description>
    <options>no_full_log</options>
    <group>logs_cleared,pci_dss_10.6.1,gpg13_10.1,gdpr_IV_35.7.d,hipaa_164.312.b,nist_800_53_AU.6,tsc_CC7.2,tsc_CC7.3,</group>
    <mitre>
      <id>T1685.005</id>
    </mitre>
  </rule>
</group>

Tout est recopié, y compris la ligne <group> complète : sans les groupes de conformité (pci_dss, gdpr…), les alertes 60117 disparaîtraient des tableaux de bord de conformité. On en profite pour corriger la technique ATT&CK : la règle fournie porte T1070.004, une erreur d'étiquette expliquée au chapitre 10. La règle d'origine reste intacte dans ruleset/, et ta version la remplace même après une mise à jour.

Les erreurs fréquentes

ErreurConséquenceLa bonne pratique
Modifier un fichier de /var/ossec/ruleset/La modification disparaît à la mise à jour suivantelocal_rules.xml, et overwrite="yes" pour changer une règle fournie
Réutiliser un identifiant existant sans overwriteTa règle est ignorée (avertissement dans ossec.log), sans autre signeIdentifiants de 100000 à 120000, tenus dans un tableau
Écrire une règle sans parent, sur tout le texte du messageLente, et elle correspond à des journaux imprévusS'accrocher à une règle existante avec if_sid ou decoded_as
Oublier de redémarrer wazuh-managerLa règle marche dans wazuh-logtest, pas sur les vrais événementsRedémarrer, puis vérifier ossec.log
Mettre au niveau 0 un événement bruyant entierLa détection est aveugléeUne exception étroite, ou mieux : corriger la cause

Exercices

Exercice 1 sur 3 Écrire une règle pour une ligne donnée Réussi À revoir

Le compte deploy sert à déployer le site sur srv-web, uniquement pendant les heures ouvrées. Sur ce serveur, une connexion réussie produit cette ligne, que Wazuh classe avec la règle 5715 « sshd: authentication success. », de niveau 3, en décodant l'utilisateur et l'adresse source :

text
Nov  4 23:41:07 srv-web sshd-session[5120]: Accepted password for deploy from 192.168.10.71 port 50322 ssh2

Écris une règle de niveau 12 qui se déclenche quand deploy se connecte avec succès entre 19 h et 8 h, et une seconde de niveau 12 quand il se connecte depuis une adresse hors du VLAN d'administration, à toute heure.

Voir la réponseMasquer la réponse de l'exercice 1
xml
<group name="sorbier,sshd,">
  <rule id="100140" level="12">
    <if_sid>5715</if_sid>
    <user>^deploy$</user>
    <time>7 pm - 8 am</time>
    <description>Sorbier : connexion SSH du compte deploy en dehors des heures ouvrées</description>
    <mitre>
      <id>T1078</id>
    </mitre>
    <group>authentication_success,</group>
  </rule>

  <rule id="100141" level="12">
    <if_sid>5715</if_sid>
    <user>^deploy$</user>
    <srcip negate="yes">192.168.10.208/28</srcip>
    <description>Sorbier : connexion SSH du compte deploy hors du VLAN d'administration</description>
    <mitre>
      <id>T1078</id>
    </mitre>
    <group>authentication_success,</group>
  </rule>
</group>

Les deux s'accrochent à 5715, qui a déjà reconnu une connexion réussie et décodé l'utilisateur. La ligne de l'énoncé (23 h 41, depuis 192.168.10.71) remplit les deux conditions : seule la première règle qui correspond dans l'arbre donne l'alerte, mais les deux sont de niveau 12, et chacune couvre un cas que l'autre ne voit pas. T1078 Valid Accounts : un compte légitime utilisé de façon anormale, peut-être par quelqu'un qui a volé son mot de passe. Une meilleure correction de fond : n'autoriser deploy qu'avec une clé SSH et depuis le VLAN 99 (AllowUsers deploy@192.168.10.208/28 dans sshd_config).

Tu avais trouvé ?

Exercice 2 sur 3 Un décodeur et sa règle Réussi À revoir

Le script de sauvegarde du site, sur srv-web, écrit cette ligne quand une sauvegarde échoue :

text
Nov  4 02:15:07 srv-web sauvegarde-site[4410]: ECHEC destination=srv-fichiers motif=espace_insuffisant

Écris le décodeur (parent et enfant) qui extrait le résultat dans le champ action et le motif dans extra_data, puis une règle de niveau 8 qui alerte sur chaque échec.

Voir la réponseMasquer la réponse de l'exercice 2

Dans local_decoder.xml :

xml
<decoder name="sauvegarde-site">
  <program_name>^sauvegarde-site$</program_name>
</decoder>

<decoder name="sauvegarde-site-champs">
  <parent>sauvegarde-site</parent>
  <regex>^(\w+) destination=\S+ motif=(\S+)</regex>
  <order>action, extra_data</order>
</decoder>

Dans local_rules.xml :

xml
<group name="sorbier,sauvegarde,">
  <rule id="100150" level="0">
    <decoded_as>sauvegarde-site</decoded_as>
    <description>Sauvegarde du site : événements du script</description>
  </rule>

  <rule id="100151" level="8">
    <if_sid>100150</if_sid>
    <action>ECHEC</action>
    <description>Sauvegarde du site : échec de la sauvegarde</description>
  </rule>
</group>

destination=\S+ est reconnu sans être capturé, puisqu'on ne demande que deux champs. On teste avec wazuh-logtest : la phase 2 doit afficher action: 'ECHEC' et extra_data: 'espace_insuffisant', la phase 3 la règle 100151. Un échec de sauvegarde n'est pas une attaque, mais c'est une menace sur la disponibilité : un rançongiciel qui frapperait le lendemain trouverait une sauvegarde absente (cours Sauvegarde et PRA). Cette alerte intéresse autant la supervision que le SIEM.

Tu avais trouvé ?

Exercice 3 sur 3 Une exception trop large Réussi À revoir

Pour faire taire les alertes du copieur, un collègue a écrit l'exception ci-dessous. Explique pourquoi elle est dangereuse et corrige-la.

xml
<rule id="100131" level="0">
  <if_sid>60122</if_sid>
  <field name="win.eventdata.ipAddress">192.168.10</field>
  <description>Bruit réseau interne</description>
</rule>
Voir la réponseMasquer la réponse de l'exercice 3

L'expression 192.168.10 (sans ancre) correspond à toute adresse du réseau interne : tous les échecs de connexion venant de l'intérieur deviennent invisibles, et les règles de force brute qui comptent le groupe authentication_failed ne les comptent plus. Or c'est précisément depuis un poste interne compromis qu'un attaquant essaie des mots de passe. La description vague ne permet pas non plus de savoir pourquoi l'exception existe ni quand la retirer. La version correcte est la règle 100130 du chapitre : le compte et l'adresse exacts du copieur, ancrés (^…$), avec une description qui cite le ticket.

Tu avais trouvé ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Contrôle d'intégrité des fichiers (FIM) avec WazuhRevoir le sommaire du cours