Santa dans Wazuh, du bruit à l'alerte qui parle

Un blocage Santa seul, c'est du bruit. Corrélé dans Wazuh, c'est une détection. Apprends à ton SIEM à décoder puis agréger les événements Santa.

Santa dans Wazuh, du bruit à l'alerte qui parle

À l’épisode précédent, tu as appris à écrire tes propres règles Wazuh, à partir d’un log réel et d’une question précise. Aujourd’hui, tu appliques ce geste à une source que tes Mac produisent déjà, Santa.

Parce que le branchement de base, tu l’as posé à l’épisode Santa. Tes Mac ont appris à remonter chaque blocage dans Wazuh, et un binaire refusé s’affiche dans ton tableau de bord, à côté de tes rejets mail et de tes blocages réseau. Sauf que ce que tu as branché là, c’était le tuyau, pas l’intelligence. Chaque blocage arrive seul, au même niveau, sans que rien ne le relie au suivant. Et un blocage tout seul, ça ne prouve pas grand-chose.

C’est exactement le sujet du jour. Un blocage Santa isolé, c’est du bruit. Corrélé dans Wazuh, c’est une détection. On va apprendre à ton SIEM à décoder proprement ces événements, à distinguer un vrai verrou d’un simple signalement, puis à les agréger, pour qu’une rafale de refus ou un blocage suivi d’un autre signal devienne un incident nommé, pas cinq alertes éparpillées.

Ce tuto vise la même branche que toute la série, version de référence 4.14.7. Prévois une petite heure, un accès shell au manager, et un Mac sous Santa qui crache de vraies lignes.

À lire d’abord : Wazuh, écris la règle qui capte ce que tu cherches vraiment, et pour la lecture avant l’écriture, Ton SIEM souverain, lis-le vraiment puis fais-le passer à l’action.


Ce qu’il te faut

Rien de neuf à installer. Tout est hérité des épisodes précédents.

  • Santa en Lockdown sur au moins un Mac, qui journalise dans /var/db/santa/santa.log. C’est l’acquis de l’épisode Santa, on ne le refait pas.
  • L’agent Wazuh sur ces Mac, celui qui lit déjà ce journal et l’expédie au manager.
  • Un accès shell au manager, ton VPS de la série. Les décodeurs et les règles vivent dans des fichiers, sur cette machine et sur elle seule.
  • De vraies lignes de blocage sous les yeux. Comme pour toute règle, on ne devine pas un format, on le lit. Provoque un DENY en lançant un binaire que tu n’as pas allowlisté, et garde la ligne au chaud.

Cette exigence n’a rien d’accessoire. Écrire une règle de corrélation sans voir les événements réels qu’elle est censée relier, c’est deviner deux fois.


Ce que ça change, ce que ça ne change pas

Ce que ça change. À la fin, tu ne reçois plus cinq alertes identiques et muettes quand un même binaire se cogne cinq fois à la porte. Tu reçois une seule alerte, plus haute, qui te dit lequel, combien de fois, et sur quelle machine. Le blocage cesse d’être une ligne de plus, il devient un fait qualifié.

Ce que ça ne change pas. Tu regardes et tu qualifies, tu ne coupes toujours rien. Bannir une adresse, tuer un processus, couper une session en réaction à l’alerte, c’est un autre étage, et on le garde éteint aujourd’hui, exactement comme au premier épisode de la série. Ici, on apprend à ton SIEM à comprendre ce qu’il voit, pas encore à agir seul.


Un blocage seul ne prouve rien

Repartons de ce que tu as branché à l’épisode Santa. Une règle qui sonne au niveau 10 dès qu’une ligne porte decision=DENY. Elle t’a rendu service, elle t’a montré que la remontée fonctionnait. Mais elle a deux angles morts, et ce sont eux qui transforment un bon outil en machine à angoisse.

Premier angle mort, elle ne distingue pas le mode. Souviens-toi de la progression de l’épisode Santa, on démarre en Monitor pour cartographier, puis on bascule en Lockdown. Or ces deux modes ne racontent pas la même histoire. En Lockdown, un DENY, c’est un binaire qui a été empêché de tourner. En Monitor, Santa laisse tout passer et journalise en ALLOW, la raison seule t’apprenant qu’il aurait bloqué en Lockdown. Traiter les deux au même niveau, c’est hurler pour des exécutions qui ont réellement eu lieu, et se noyer pendant toute la phase de déploiement.

Second angle mort, elle traite chaque blocage comme un événement final. Un DENY isolé, la plupart du temps, c’est un utilitaire de mise à jour maison qui se relance tout seul, ou un binaire de test lancé une fois puis oublié. Un faux positif de calibration, pas une attaque. Le signal n’est pas dans le blocage, il est dans le motif. Une tentative, c’est un fait divers. La même, cinq fois en une minute, c’est un mode opératoire.

Décoder, distinguer, corréler. C’est le plan des quatre sections qui suivent.


Étape 1, faire lire Santa en clair

Pour corréler, il te faut des champs propres, nommés, typés. Le chemin du binaire d’un côté, le mode de l’autre, la décision à part. La bonne nouvelle, c’est que Santa te donne déjà tout ça, en clair, dans le format que tu as posé à l’épisode Santa.

Ce format, c’est le format file, celui de la clé EventLogType réglée sur file. Une ligne par exécution, un préfixe horodaté, puis une suite de paires clé=valeur collées par des barres verticales. Voici une ligne de blocage, identifiants neutralisés.

[2026-01-15T09:33:12.148Z] I santad: action=EXEC|decision=DENY|reason=BINARY|sha256=9f2c…|pid=42117|user=mack|mode=L|path=/usr/local/bin/updater|machineid=…

Tout est là, à plat. decision=DENY, c’est le verdict. mode=L, c’est le Lockdown, la lettre qui dit qu’un binaire a été empêché, pas juste signalé. reason=BINARY, c’est la règle qui a tranché. path=, c’est le coupable. Des clés courtes, des valeurs courtes, une ligne par événement. Ce n’est pas du JSON, donc Wazuh ne le déplie pas tout seul, tu lui écris un petit décodeur qui nomme les champs. En échange, tu as un format stable qui ne bougera pas d’une version à l’autre.

Santa peut aussi écrire en JSON, mais ce rendu est marqué BETA et instable, alors on décode le file qui est déjà en place et que l’agent Wazuh lit déjà.

Côté agent, rien à toucher. Le bloc <localfile> de l’épisode Santa lit déjà /var/db/santa/santa.log en syslog, poussé à tous tes Mac par la configuration partagée du groupe macos.

<localfile>
  <location>/var/db/santa/santa.log</location>
  <log_format>syslog</log_format>
</localfile>

Tout se joue côté manager, dans le décodeur. À l’épisode Santa, tu en as posé un qui extrait déjà la décision, la raison et le chemin, dans /var/ossec/etc/decoders/local_decoder.xml. On l’enrichit d’un seul champ, le mode, parce que c’est lui qui va tout arbitrer par la suite.

<decoder name="santa">
  <prematch>santad: action=EXEC</prematch>
</decoder>

<decoder name="santa-decision">
  <parent>santa</parent>
  <regex type="pcre2">decision=(\w+)\|reason=(\w+)\|.*?\|mode=(\w+)\|path=([^|]+)</regex>
  <order>santa_decision,santa_reason,santa_mode,santa_path</order>
</decoder>

Un mot sur ce motif, parce qu’il y a un piège que tu ne vois pas venir. Dans le champ args, Santa réécrit toute barre verticale littérale en <pipe> et tout saut de ligne en \n, justement pour qu’un argument ne casse jamais le découpage. Conséquence, ton décodeur doit s’ancrer sur des clés connues, decision=, mode=, path=, et jamais découper naïvement sur la dernière barre de la ligne. C’est pour ça que path=([^|]+) s’arrête à la barre suivante, celle qui précède args, au lieu d’avaler le reste. Tu extrais le chemin, pas la queue de la ligne.


Étape 2, lire les vrais noms de champs

Voilà le réflexe qui t’épargne une soirée. Le format file est stable, mais l’ordre des clés et surtout leur présence dépendent de ta version de Santa et de ce que tu exécutes. Un binaire signé porte un teamid, un binaire nu n’en a pas. Le format, tu le lis sur une vraie ligne, tu ne le récites pas de mémoire.

L’outil pour le lire, c’est celui de l’épisode précédent, wazuh-logtest. Sur le manager, tu le lances, tu colles une vraie ligne de ton santa.log, et tu regardes la phase 2.

sudo /var/ossec/bin/wazuh-logtest

La phase 2 te montre chaque champ décodé, avec son nom exact. C’est là, et nulle part ailleurs, que tu confirmes que santa_decision, santa_mode et santa_path sortent bien de ta ligne. Si l’un d’eux manque, c’est le motif du décodeur qu’il faut ajuster, et tu le sais en trente secondes. Ne recharge jamais un jeu de règles sur la foi d’un champ que tu n’as pas vu tomber dans la phase 2.


Étape 3, séparer le vrai verrou du simple signalement

Avant de compter, on nomme. Trois règles de base, dans /var/ossec/etc/rules/local_rules.xml, sur le manager. Une règle chapeau qui reconnaît un événement Santa, une pour le vrai blocage, une pour le signalement Monitor qu’on veut voir sans se faire réveiller.

<group name="santa,">

  <rule id="100600" level="0">
    <decoded_as>santa</decoded_as>
    <description>Santa, événement d'exécution décodé.</description>
  </rule>

  <rule id="100601" level="10">
    <if_sid>100600</if_sid>
    <field name="santa_decision" type="pcre2">^DENY$</field>
    <field name="santa_mode" type="pcre2">^L$</field>
    <description>Santa, binaire BLOQUÉ en Lockdown, $(santa_path).</description>
    <group>santa_block,</group>
  </rule>

  <rule id="100602" level="3">
    <if_sid>100600</if_sid>
    <field name="santa_decision" type="pcre2">^ALLOW$</field>
    <field name="santa_mode" type="pcre2">^M$</field>
    <field name="santa_reason" type="pcre2">^UNKNOWN$</field>
    <description>Santa, binaire non autorisé toléré en Monitor, $(santa_path).</description>
    <group>santa_monitor,</group>
  </rule>

</group>

Regarde ce qui se joue. La 100600 est en niveau 0, elle décode et se tait, c’est le socle. La 100601 ne se déclenche que sur la conjonction d’un decision=DENY et d’un mode=L, le seul cas où un binaire a réellement été empêché. La 100602 attrape l’autre visage, un binaire que rien n’autorise, reason=UNKNOWN, qui a quand même tourné parce qu’on était en Monitor, mode=M sur un ALLOW. Elle reste au niveau 3, une trace basse que tu consultes sans qu’elle t’inonde pendant le déploiement, là où tout passe en ALLOW.

Ce sont les lettres courtes du format file qui font le tri, L contre M, DENY contre ALLOW. L’ancrage ^L$ et non L, même précaution qu’à l’épisode Santa, pour ne coller qu’à la valeur exacte et pas à un mode qui commencerait par la même lettre demain.

Le $(santa_path) dans la description injecte le chemin réel dans l’alerte. C’est ce détail qui fait qu’un message « parle », il te dit quel binaire, pas juste qu’il y en a eu un.

Une ligne Santa au format file décodée par wazuh-logtest en tableau champ, valeur, sens, puis la règle de blocage au niveau 10.

Du log brut au verdict. wazuh-logtest déplie la ligne, chaque champ prend son sens, et la 100601 lève le blocage au niveau 10.

Ces trois règles remplacent la règle unique de l’épisode Santa, celle qui sonnait sur tout DENY. Si tu la gardes en plus, tu comptes deux fois. Retire-la, ou passe son niveau à 0, et laisse ces nouvelles prendre le relais.


Étape 4, corréler, du blocage isolé à l’incident

On tient nos blocages propres. On peut enfin poser les deux questions qui transforment le bruit en détection. Wazuh sait faire deux choses en matière de corrélation, et deux seulement, mais bien. On va les prendre l’une après l’autre.

Patron A, le même binaire qui insiste

Un blocage, passe encore. Cinq blocages du même binaire en une minute, c’est un processus qui s’acharne, un script qui relance, une charge qui ne lâche pas. On veut une seule alerte, plus haute, à la place des cinq.

<group name="santa,">

  <rule id="100610" level="12" frequency="5" timeframe="60">
    <if_matched_sid>100601</if_matched_sid>
    <same_field>santa_path</same_field>
    <description>Santa, 5 blocages Lockdown du même binaire en 60s, processus insistant.</description>
    <group>santa_block,correlation,</group>
  </rule>

</group>

La mécanique est lisible. if_matched_sid ne regarde que les vrais blocages, ceux de la 100601. frequency fixe le seuil à cinq, timeframe la fenêtre à soixante secondes. same_field restreint le compte à un même chemin d’exécutable, santa_path, pour ne pas additionner cinq binaires différents. Résultat, une alerte de niveau 12 au lieu de cinq alertes de niveau 10 dispersées.

Cinq blocages identiques du même binaire au niveau 10 corrélés par Wazuh en une seule alerte de niveau 12.

Cinq refus du même binaire en une minute, une seule alerte plus haute. Le compte remplace la répétition.

Si same_field te joue des tours sur ta version, il existe un repli qui ne trompe jamais, épingler le binaire dans la règle. Tu ajoutes un <field> sur le chemin exact, et par construction tous les événements comptés sont le même binaire, sans dépendre de same_field. Moins générique, mais bétonné pour un premier essai.

<rule id="100611" level="12" frequency="5" timeframe="60">
  <if_matched_sid>100601</if_matched_sid>
  <field name="santa_path" type="pcre2">/Users/[^/]+/Downloads/updater$</field>
  <description>Santa, 5 blocages Lockdown d'un binaire précis en 60s.</description>
  <group>santa_block,correlation,</group>
</rule>

Patron B, un blocage suivi d’un autre signal

Le cœur de l’angle. « Binaire bloqué plus telle autre source égale un incident. » Un exécutable refusé sur un poste, ça peut être anodin. Un exécutable refusé, puis une connexion sortante inhabituelle depuis la même machine dans la foulée, ça mérite qu’on se lève.

Comme un événement réseau ne partage ni le chemin ni l’empreinte du binaire Santa, la clé commune réaliste, c’est l’hôte. Et ça tombe bien, le comptage de Wazuh est déjà par agent par défaut, le regroupement par machine est donc implicite.

<group name="santa,">

  <rule id="100620" level="12" timeframe="120">
    <if_matched_sid>100601</if_matched_sid>
    <if_sid>100300</if_sid>
    <description>Santa, binaire bloqué puis connexion sortante sur le même hôte en 120s.</description>
    <group>santa_block,correlation,</group>
  </rule>

</group>

La règle se déclenche sur l’événement réseau (if_sid sur la 100300), à condition qu’un blocage Santa en Lockdown (if_matched_sid sur la 100601) ait eu lieu dans les cent vingt secondes précédentes, sur le même agent. Le blocage isolé devient « exécution empêchée, puis tentative réseau », un enchaînement qui raconte une histoire.

Une réserve à connaître sur cette fenêtre. Sur Wazuh 4.14.7, un if_matched_sid avec un timeframe mais sans frequency ne borne pas la fenêtre de façon fiable, c’est un comportement documenté du moteur, la règle peut se déclencher même quand le blocage précurseur est bien plus vieux que tes cent vingt secondes. Et le correctif intuitif, frequency="1", est refusé au chargement. Prends donc la 100620 comme une corrélation d’enchaînement, pas comme une garantie stricte sur la fenêtre, et vérifie son comportement chez toi dans wazuh-logtest, avec un précurseur ancien, avant de compter dessus.

La limite, dite franchement

Wazuh corrèle bien deux cas, la même règle N fois dans une fenêtre, et une règle récente suivie d’une autre, regroupées par same_* ou par hôte. Ce qu’il ne fait pas nativement, c’est un « et » libre entre sources arbitraires, du genre « condition A d’une source ET condition B d’une autre, sans relation temporelle simple ». C’est une limitation connue et régulièrement demandée du moteur, pas un défaut de ta config.

La conséquence est pratique, pas dramatique. Tu construis tes détections sur les deux patrons ci-dessus, qui sont réellement supportés et robustes, et tu ne cherches pas à écrire une algèbre booléenne que le moteur ne tiendra pas. Deux questions bien posées valent mieux qu’une usine à gaz qui ne se déclenche jamais.


Étape 5, tester avant de recharger

On ne fait confiance à aucune de ces règles sur parole. Avant tout redémarrage, on valide dans wazuh-logtest, sur le manager.

sudo /var/ossec/bin/wazuh-logtest

Pour les règles de base, tu colles une ligne, tu regardes la phase 3 t’annoncer la 100601 au niveau 10 sur un blocage Lockdown, avec le chemin du binaire dans la description. Pour les règles de fréquence, il faut envoyer plusieurs lignes du même blocage à la suite, dans la même session, pour voir le seuil tomber et la 100610 se déclencher au niveau 12. Une ligne ne suffit pas à prouver une corrélation, c’est le principe même. Je ne recharge jamais un jeu de règles de corrélation sans avoir vu le seuil tomber pour de vrai dans le test, ça m’a épargné plus d’une fausse joie.

Quand le test est vert, et seulement là, tu recharges le manager pour activer tes règles en production.

sudo systemctl restart wazuh-manager

Un redémarrage coupe brièvement la réception des logs, le temps que le service revienne. Quelques secondes de collecte en pause, rien qui ne se perde.


Si ça ne marche pas

Problème, aucun champ ne sort, la ligne n’est pas décodée

Cause probable, le <prematch> ne colle pas à tes lignes, ou le motif d’extraction rate. Les lignes de Santa commencent par un horodatage entre crochets qui n’a pas la forme d’un log syslog classique, et le pré-décodage peut demander un ajustement selon ta version de Wazuh.

Solution, colle une vraie ligne dans wazuh-logtest et lis la phase 2. Si le décodeur santa n’est même pas reconnu, c’est le <prematch> qu’il faut élargir. Si le décodeur sort mais que santa_mode manque, c’est le motif regex qu’il faut réaligner sur l’ordre réel de tes clés.

Problème, tu es inondé d’alertes de blocage en pleine cartographie

Cause probable, tu es encore en Monitor et tu comptes les mauvais événements, ou tu as gardé l’ancienne règle DENY unique en plus des nouvelles.

Solution, vérifie que ta règle de blocage exige bien mode=L, pas seulement decision=DENY. En Monitor, un binaire non autorisé doit tomber en 100602 au niveau 3, pas en alerte haute. Et retire l’ancienne règle de l’épisode Santa, ou passe-la en niveau 0.

Problème, les noms de champs ne correspondent à rien

Cause probable, l’ordre ou la présence des clés varie sur ta version, ou tes binaires ne portent pas les mêmes champs, un binaire signé a un teamid, un binaire nu n’en a pas.

Solution, ne copie jamais un motif de décodeur à l’aveugle. Lis la phase 2 de wazuh-logtest sur une vraie ligne de ta machine, et réaligne le regex et l’order sur ce que tu y vois réellement.

Problème, la corrélation ne se déclenche jamais dans le test

Cause probable, tu n’as envoyé qu’une seule ligne, ou ta fenêtre est trop courte, ou same_field ne compte pas comme tu l’imagines.

Solution, envoie plusieurs lignes successives dans la même session wazuh-logtest. Élargis un peu le timeframe. Et si le doute persiste sur same_field, bascule sur le repli qui épingle le chemin du binaire dans un <field>.

Problème, wazuh-logtest voit tes règles mais la production les ignore

Cause probable, tu n’as pas rechargé le manager, ou tu as édité décodeurs et règles sur le Mac au lieu du manager.

Solution, sudo systemctl restart wazuh-manager. Et vérifie que local_rules.xml et local_decoder.xml sont bien sur le manager, pas sur l’agent. Le grand classique de l’épisode Santa, les règles écrites du mauvais côté du tuyau.


Pour les pressés

Ce que fait cet article. Il part de la remontée Santa de base que tu as déjà branchée, et la transforme en vraies détections. Décoder proprement les événements Santa, séparer un blocage réel d’un simple signalement, puis corréler pour qu’une rafale ou un enchaînement devienne un incident unique et qualifié.

Concrètement, la marche à suivre.

  1. Rien à basculer côté Santa, il écrit déjà en file depuis l’épisode Santa, une ligne clé=valeur par exécution. C’est ce format-là qu’on décode.
  2. L’agent lit déjà le journal, un <localfile> en <log_format>syslog</log_format> sur /var/db/santa/santa.log, poussé par le groupe macos. Rien à changer.
  3. Enrichis le décodeur de l’épisode Santa, dans local_decoder.xml sur le manager, pour capter aussi le mode, santa_decision, santa_reason, santa_mode, santa_path.
  4. Lis les vrais champs dans la phase 2 de wazuh-logtest, ne les copie pas les yeux fermés.
  5. Écris les règles de base dans local_rules.xml sur le manager, un vrai blocage égale decision=DENY plus mode=L au niveau 10, un signalement Monitor reste discret au niveau 3.
  6. Corrèle, if_matched_sid plus frequency et timeframe plus same_field pour N blocages du même binaire, if_matched_sid plus if_sid pour un blocage suivi d’un autre signal sur le même hôte.
  7. Teste dans wazuh-logtest en collant plusieurs lignes pour voir le seuil tomber, puis sudo systemctl restart wazuh-manager.
  8. Garde tes identifiants au-dessus de 100000, et retire l’ancienne règle DENY unique de l’épisode Santa, ces nouvelles la remplacent.

En résumé

Tu es parti d’un blocage qui remontait, seul, muet, au même niveau que le voisin. Tu repars avec un SIEM qui sait ce qu’il regarde.

La leçon n’est pas dans la syntaxe XML, tu la tiendras en trois règles. Elle est dans l’étagement. Un vrai verrou n’est pas un signalement, tu les sépares. Un blocage isolé n’est pas un incident, tu attends le motif. Une rafale du même binaire, un blocage suivi d’un autre signal, voilà ce qui mérite de te réveiller, le reste reste au niveau bas, consultable et silencieux.

Décoder, distinguer, corréler. Trois gestes, et un tableau de bord qui, enfin, te parle au lieu de crépiter.


Pour creuser, la doc officielle

Toute la syntaxe manipulée ici est documentée, page par page. Voici où vérifier chaque balise que tu viens de poser.


Et après, laisser le SIEM agir

Tu sais maintenant faire dire à Wazuh non seulement ce que tes Mac bloquent, mais ce que ces blocages veulent dire ensemble. Le cran suivant, c’est de le laisser réagir tout seul, couper une connexion, isoler une machine, sans qu’il se retourne contre toi sur un faux positif.

À lire bientôt : la réponse active, quand le SIEM cesse de regarder et se met à couper.

Et si tu veux revoir le poste de travail lui-même, le contrôle d’exécution d’où sortent ces blocages.

À lire : Santa sur tes Mac, le binaire non invité reste à la porte

Des termes techniques ? Consulte le glossaire.