Einleitung
Beim Bau eigener Wazuh-Rulesets entsteht schnell der Wunsch nach einer „Auffangregel“: Alles, was nicht spezifisch gematcht wird, soll trotzdem klassifiziert, geloggt und später analysierbar sein. Genau hier stolpern viele – weil Wazuh Regeln nicht nach Rule-ID priorisiert, sondern nach Evaluierungslogik (insbesondere Severity/Level, Rule-Chain und Matching). Das Ergebnis: Eine Catch-all-Regel feuert immer, während die eigentlich spezifischen Regeln nie zum Zug kommen. Dieser Artikel erklärt die Mechanik und zeigt ein praxistaugliches Design, das sowohl Korrelationsregeln (<if_matched_sid>) ermöglicht als auch Alert-Spam verhindert.
Ausgangslage / Problemstellung
In einem Custom-Ruleset für MikroTik/Firewall-Logs existieren:
- Eine Basisregel (Parent) für die Decoder-Klasse
firewall-base - Drei spezifische Child-Regeln, die über
<field name="in">/<field name="out">Traffic-Richtungen unterscheiden (LAN→WAN, LAN→LAN, WAN→LAN) - Eine Default-/Audit-Regel, die alle übrigen Events auffangen soll
Trotz spezifischer Child-Regeln wird immer die Audit-Regel 120000 ausgelöst. Zusätzlich besteht die Anforderung, später Korrelationsregeln wie Portscan-Detection zu nutzen:
<rule id="100014" level="12" frequency="20" timeframe="60">
<if_matched_sid>100004</if_matched_sid>
<same_srcip />
<same_dstip />
<different_dstport />
<description>Port scan detected: IP $(srcip) scan $(dstip).</description>
</rule>
Nebenbedingung:
- Die „Basis“-Traffic-Regeln sollen möglichst keine Alerts erzeugen (UI nicht „zumüllen“), aber als Grundlage für Korrelation zählen können.
Technische Analyse
1) Rule-ID ist keine Priorität
Die Nummer 120000 ist nicht „stärker“ als 100002. Rule-IDs dienen Identifikation, nicht Sortierung.
2) Warum die Catch-all-Regel gewinnt
Wenn mehrere Regeln denselben Parent haben (<if_sid>100000</if_sid>), hängt das Ergebnis primär von der Rule-Sverity (level) und sekundär von der Lade-/Reihenfolge ab. Entscheidend: Eine Catch-all-Regel ohne weitere Bedingungen matcht alles, was den Parent matched.
In der Praxis bedeutet das:
- Hat die Catch-all-Regel ein höheres Level als die spezifischen Regeln, wird sie bevorzugt und kann die Verarbeitung „beenden“.
- Selbst wenn spezifische Regeln existieren, können sie praktisch nie greifen, wenn vorher eine „breite“ Regel mit höherem Level erfolgreich matcht.
3) Das eigentliche Dilemma: „Nicht alerten“ vs. „Korrelierbar sein“
Viele Nutzer setzen Level 0 oder sehr niedrig, um keine Alerts zu bekommen. Gleichzeitig verlangt Korrelation (frequency/timeframe + <if_matched_sid>) eine Regelbasis, die als „match“ im Engine-Kontext nutzbar ist.
Der Fehler ist oft nicht „Level zu hoch“, sondern:
- Catch-all-Level ist nicht unterhalb der spezifischen Regeln
- und/oder es fehlt eine zweistufige Default-Kette, um Audit-Klassifizierung und Alert-Triggering sauber zu trennen.
Lösung / Best Practices
Zielarchitektur
- Parent-Regel klassifiziert die Decoder-Familie.
- Spezifische Child-Regeln haben ein Level, das:
- hoch genug ist, um gegenüber Catch-all zu gewinnen
- aber niedrig genug, um bei Standard-Alert-Schwelle keine Alerts zu erzeugen
- Catch-all wird zweistufig gebaut:
- Stage 1: immer matchen, sehr niedriger Level (klassifizieren, gruppieren)
- Stage 2: optionaler Alert-Trigger, nur wenn gewünscht (oder später per Child-Regeln/Korrelation)
Konkrete Umsetzung: Spezifische Regeln auf Level 2, Catch-all als 2-Stufen-Default
Unter Standard-Konfiguration (<log_alert_level> typischerweise 3) erzeugen Level 2-Regeln keine Alerts, sind aber „echt genug“, um als Basis für Korrelationslogik zu dienen und gegenüber noch niedrigeren Defaults zu gewinnen.
<group name="mikrotik,">
<!-- Parent: Firewall-Decoder-Klasse -->
<rule id="100000" level="0">
<decoded_as>firewall-base</decoded_as>
<description>Logs Firewall</description>
</rule>
<!-- Spezifische Richtungsregeln: nicht alertend (bei log_alert_level=3), aber priorisiert -->
<rule id="100002" level="2">
<if_sid>100000</if_sid>
<field name="in">LAN</field>
<field name="out">WAN|WAN2|WAN3</field>
<description>Outbound stream detected: $(in) to $(out).</description>
<group>firewall_traffic,mikrotik</group>
</rule>
<rule id="100003" level="2">
<if_sid>100000</if_sid>
<field name="in">LAN</field>
<field name="out">LAN</field>
<description>Internal traffic detected at the antenna.</description>
<group>firewall_traffic,mikrotik</group>
</rule>
<rule id="100004" level="2">
<if_sid>100000</if_sid>
<field name="in">WAN|WAN2|WAN3</field>
<field name="out">LAN</field>
<description>External traffic detected at the antenna.</description>
<group>firewall_traffic,mikrotik</group>
</rule>
<!-- Catch-all Stage 1: immer matchen, aber noch niedriger als die spezifischen Regeln -->
<rule id="120000" level="1">
<if_sid>100000</if_sid>
<description>Firewall audit logs (catch-all)</description>
<group>audit,mikrotik</group>
</rule>
<!-- Catch-all Stage 2: optional alertend, falls man wirklich Alerts für "unclassified" will -->
<rule id="120001" level="3">
<if_sid>120000</if_sid>
<description>Firewall audit logs (unclassified event)</description>
<group>audit,mikrotik</group>
</rule>
</group>
Wichtige Details:
- Trailing comma in
<group>vermeiden (<group>audit,</group>ist fehleranfällig). Besser:audit,mikrotikohne Schlusskomma. - Die Catch-all Stage 1 ist bewusst Level 1, damit sie nur greift, wenn Level-2-Regeln nicht greifen.
- Stage 2 ist optional: Wer gar keine Alerts aus dem Catch-all möchte, lässt
120001weg oder setzt Level unter die Alert-Schwelle.
Korrelationsregel (Portscan) sauber anbinden
Mit obigem Modell bleibt 100004 Level 2 – und kann damit problemlos als Basis für <if_matched_sid> verwendet werden, ohne die UI zu fluten.
Beispiel (wie gehabt), aber Gruppen sauber:
<rule id="100014" level="12" frequency="20" timeframe="60">
<if_matched_sid>100004</if_matched_sid>
<same_srcip />
<same_dstip />
<different_dstport />
<description>Port scan detected: IP $(srcip) scan $(dstip).</description>
<group>port_scan,network,mikrotik</group>
</rule>
Damit passiert genau das Gewünschte:
- Einzelne WAN→LAN-Verbindungen (Level 2) erzeugen keine Alerts.
- Die Korrelation (20 Events/60s) triggert auf Level 12 sehr wohl und ist sauber sichtbar.
Lessons Learned / Best Practices
- Regelpriorität ist nicht die Rule-ID, sondern ergibt sich aus Evaluierungslogik (Severity/Level und Rule-Chain).
- Catch-all-Regeln brauchen immer das niedrigste Level innerhalb einer Geschwistergruppe, sonst „erschlagen“ sie Spezifika.
- Für „zählbare, aber nicht alertende“ Basisevents eignen sich Level 1–2 (abhängig von
log_alert_level), nicht Level 0 als Standardlösung. - Wenn man sowohl „Audit-Klassifizierung“ als auch „optional alerten“ will, ist ein zweistufiges Default-Pattern (Stage 1/Stage 2) der stabilste Ansatz.
<group>ohne Schlusskomma schreiben und konsistente Taxonomie nutzen (z. B.mikrotik,audit,firewall_traffic,port_scan).
Fazit
Dass eine Catch-all-Regel „gewinnt“, liegt nicht an der höheren Rule-ID, sondern daran, dass sie ohne Bedingungen alles matcht und – je nach Level – in der Evaluierung vor den spezifischen Regeln priorisiert wird. Mit Level-2-Spezifikaregeln (nicht alertend bei Standard-Alert-Schwelle) und einer zweistufigen Default-Regel (Level 1 → optional Level 3) erhält man das beste aus beiden Welten: vollständige Audit-Abdeckung ohne Alert-Flut und gleichzeitig eine saubere Basis für Korrelationsregeln wie Portscan-Detection.
Mehr zu Wazuh …
https://wazuh.com/?utm_source=ambassadors&utm_medium=referral&utm_campaign=ambassadors+program
Mehr zum Wazuh Ambassador Program …
https://wazuh.com/ambassadors-program/?utm_source=ambassadors&utm_medium=referral&utm_campaign=ambassadors+program
https://wazuh.slack.com/archives/C07CCCCGHHP/p1769030097566099