Einleitung
Wer in Wazuh eigene Decoder baut, erwartet intuitiv häufig ein „Pipeline“-Verhalten: Der Parent extrahiert Basisfelder, Child-Decoder ergänzen weitere Felder – und Regeln können anschließend exakt auf den passenden Child matchen. In Wazuh ist das Decoder-Processing jedoch bewusst anders konstruiert: Es ist auf schnelle Klassifizierung und deterministisches Branching optimiert, nicht auf additive Feldvererbung. Das fällt besonders bei generischen Formaten wie CEF auf, wenn ein Basisdecoder Header-Felder extrahiert und ein Child nur noch einzelne Extension-Felder zieht – und plötzlich „verschwinden“ die Parent-Felder im Ergebnis.
Dieser Beitrag erklärt die Mechanik hinter Parent/Child und Sibling-Decodern, ordnet das Verhalten in Wazuh 4.14.x ein und zeigt ein robustes Designmuster für CEF, das sowohl Erweiterbarkeit als auch Rule-Matching sauber abbildet.
Ausgangslage / Problemstellung
Ein generischer CEF-Basisdecoder (cef-base) matcht zuverlässig und extrahiert z. B. vendor, product, product_version, class_id, event_name, severity. Sobald jedoch ein Child-Decoder hinzugefügt wird (z. B. zum Extrahieren von msg= aus der CEF-Extension), zeigt der Decoder-Test in Phase 2 nur noch das Feld des Child-Decoders (z. B. msg) – die Parent-Felder erscheinen nicht mehr.
Zusätzlich wird beobachtet:
- „Sibling“-Decoder mit gleichem Parent und unterschiedlichen Namen verhalten sich so, als würde nur einer greifen.
- Regeln mit
<decoded_as>matchen nur den Parent-Decodernamen, nicht den Child-Namen.
Technische Analyse
1) Warum Parent/Child in Wazuh nicht „additiv“ ist
Wazuh beschreibt das Decoder-Matching als sequenziellen Prozess: Zunächst werden Decoder ohne Parent geprüft. Sobald ein Decoder matcht, wird nicht weiter nach anderen Decodern gesucht, sondern nur noch dessen Children werden evaluiert. Das ist ein zentraler Punkt, weil es erklärt, warum man keine „Parallel-Auswertung“ mehr bekommt, sobald man in einen Parent/Child-Zweig abgebogen ist.
Gleichzeitig ist das Parent/Child-Konzept in Wazuh primär eine Struktur- und Klassifizierungsbeziehung: Der Parent bildet den Einstiegspunkt, die Children bilden Varianten oder Spezialisierungen. Wazuhs Doku zum parent-Element betont die Hierarchie (Child triggert nur, wenn Parent zuvor matchte) – es ist kein Vererbungsmechanismus im Sinne eines Feld-Mergings.
Praktische Konsequenz in vielen Setups:
Wenn der Child-Decoder matcht, wird er zur effektiven Extraktionsschicht für diesen Logtyp. Felder, die im Parent bereits extrahiert wurden, werden nicht automatisch mitgeführt bzw. erscheinen im Test nicht mehr so, wie man es aus „additiven Parsern“ erwarten würde. Das wirkt unlogisch, ist aber konsistent mit dem Branching-Ansatz.
2) Warum „nur ein Sibling greift“ – und weshalb „Sibling“ in Wazuh ein Designmuster ist
Der Begriff „Sibling Decoder“ ist in Wazuh nicht einfach „zwei Children unter dem gleichen Parent“, sondern ein Decoder-Building-Pattern, das die Parent/Child-Logik gezielt so nutzt, dass mehrere Decoder auf derselben Ebene „modular“ extrahieren können. Wazuh beschreibt explizit, dass Sibling-Decoders die Matching-Logik so ausnutzen, dass nach einem Match auch „Geschwister“ geprüft werden, um Stück für Stück Informationen zu extrahieren.
In der Praxis wird das häufig dadurch erreicht, dass mehrere Decoder denselben „Einstieg“ teilen und so konstruiert sind, dass sie in derselben Matching-Kette wiederholt zur Anwendung kommen (statt als klassische, einmalige Spezialisierung). Das ist der Grund, warum viele funktionierende Beispiele mehrere Decoder mit gleichem Namen (oder zumindest gleichem Root-Decoder in der Kette) zeigen: Man baut damit eine modulare Extraktion, die nicht durch „ein einzelnes Child gewinnt“ abbricht.
3) Warum <decoded_as> auf den Parent matcht (und nicht auf den Child)
Dieses Verhalten ist in der Community seit Langem als Stolperstein dokumentiert: In Decoder-Tests und im Ruleset-Matching wird typischerweise der Root-Decodername der Kette angezeigt bzw. erwartet. Ein klassisches Beispiel ist ein GitHub-Issue, in dem ein Child-Decoder korrekt extrahiert, eine Regel mit <decoded_as> auf den Child-Namen aber nicht matcht – erst <decoded_as> auf den Parent führt zum Treffer.
Für die Rule-Strategie bedeutet das:
<decoded_as>eignet sich hervorragend, um auf den Einstiegspunkt (Root/Parent) zu matchen.- Spezialisierung erfolgt danach über
<field>-Bedingungen auf extrahierte Felder oder über Rule-Chaining (z. B.<if_sid>), nicht über „Child-Decodername als decoded_as“.
4) Relevanz für CEF
CEF ist zweigeteilt: ein strukturierter Header (Pipe-separiert) plus Key-Value-Extension. Der Header ist stabil, die Extension ist dynamisch. Genau dafür ist das Sibling-Pattern ideal: Header einmal klassifizieren, Extension-Felder modular extrahieren, ohne dass optionale Felder einen monolithischen Regex sprengen.
Lösung / Best Practices
A) CEF-Design in Wazuh: Klassifizieren + modular extrahieren
Ziel: Ein Root-Decoder, der zuverlässig CEF erkennt, plus modulare Extraktionseinheiten für Header und Extension – ohne zu erwarten, dass Parent-Felder automatisch „geerbt“ werden.
1) Root-Decoder (stark spezifisch, nur Klassifizierung)
<decoder name="cef">
<prematch>^CEF:\d+\|</prematch>
</decoder>
Hinweis: prematch ist bewusst streng gehalten. Wazuh empfiehlt, Prematches so spezifisch wie möglich zu gestalten, um False Positives zu vermeiden.
2) Header-Extraktion als eigener Decoder im selben Zweig
Statt die Header-Felder im Root zu extrahieren und dann in Children „zu verlieren“, wird die Header-Extraktion als nachgelagerter Decoder unter dem Root platziert:
<decoder name="cef-header">
<parent>cef</parent>
<regex type="pcre2">^CEF:(\d+)\|((?:\\.|[^|])*)\|((?:\\.|[^|])*)\|((?:\\.|[^|])*)\|(?:((?:\\.|[^|])*)\|)?((?:\\.|[^|])*)\|(\d+)\|</regex>
<order>cef_version, vendor, product, product_version, class_id, event_name, severity</order>
</decoder>
3) Extension-Felder als modulare Decoder (Sibling-Pattern nutzen)
Für dynamische KV-Paare (z. B. msg=, src=, dst=, dvc=) empfiehlt sich ein modularer Ansatz. Wichtig ist, dass diese Decoder nicht als „ein einziges Child gewinnt“ konstruiert sind, sondern als modulare Extraktion, wie es Wazuh für Sibling-Decoders beschreibt.
Beispiel: msg= aus der Extension extrahieren, ohne den Rest zu zerstören:
<decoder name="cef-ext-msg">
<parent>cef</parent>
<regex type="pcre2">\smsg=([^=]*?)(?:\s+\w+=|$)</regex>
<order>msg</order>
</decoder>
Analog können weitere Felder folgen (src=, suser=, dhost=, …). Der Vorteil: Optionale Felder brechen nicht die gesamte Dekodierung und die Decoder bleiben lesbar.
B) Rule-Design: <decoded_as> auf Root, Spezialisierung über Felder
Da <decoded_as> auf den Root-Decoder ausgelegt ist, sollte die Regelstruktur so aussehen:
<rule id="100100" level="3">
<decoded_as>cef</decoded_as>
<description>CEF event received</description>
</rule><rule id="100110" level="7">
<if_sid>100100</if_sid>
<field name="vendor">^Ubiquiti$</field>
<description>CEF Ubiquiti event</description>
</rule><rule id="100120" level="10">
<if_sid>100110</if_sid>
<field name="event_name">Admin Accessed</field>
<description>Ubiquiti admin access via UniFi</description>
</rule>
Das folgt der praktischen Realität des Decoder-/Rule-Matchings und umgeht die Falle „decoded_as auf Child-Decodername“.
C) Erwartungsmanagement: Wann Parent/Child sinnvoll ist – und wann nicht
- Parent/Child eignet sich für Strukturvarianten, bei denen genau eine Variante pro Log greift (z. B. „Feld vorhanden“ vs. „Feld fehlt“).
- Sibling-Pattern eignet sich für dynamische Logs, bei denen du „so viel wie möglich“ extrahieren möchtest, auch wenn Reihenfolge/Präsenz variiert. Genau dafür ist es dokumentiert.
Lessons Learned / Best Practices
- Decoder sind Branching, nicht Pipeline: Sobald ein Decoder matcht, wird der Suchraum auf dessen Children eingeschränkt. Das beeinflusst stark, wie man „Erweiterungen“ modellieren sollte.
<decoded_as>auf den Einstiegspunkt setzen: Root/Parent ist der stabile Anker. Spezialisierung erfolgt über Felder und Rule-Chaining, nicht über Child-Namen.- CEF am besten modular: Header stabil extrahieren, Extension als modulare Extraktionseinheiten (Sibling-Pattern), damit optionale Keys nicht alles brechen.
- Prematch nach Syslog-Header beachten: Bei syslog-ähnlichen Logs wird
prematchnur auf den Teil nach dem Header angewendet. Das ist wichtig, wenn CEF z. B. in Syslog verpackt transportiert wird.
Fazit
Das beobachtete Verhalten ist in Wazuh erwartbar: Parent/Child dient in erster Linie der Klassifizierung und Strukturverzweigung, nicht dem additiven Feld-Merging. Sobald ein Child matcht, ist es normal, dass sich die Ausgabe nicht wie eine „Pipeline“ verhält. Für CEF ist deshalb ein Design aus einem strengen Root-Decoder plus modularen Extraktionsdecodern (Sibling-Pattern) am zuverlässigsten. Regeln sollten konsequent auf den Root (<decoded_as>) matchen und danach über Felder/Rule-Chaining spezialisieren. Das Ergebnis ist ein wartbares, skalierbares Decoder- und Regelwerk, das auch bei dynamischen CEF-Extensions stabil bleibt.
Quellenverweise (Copy & Paste)
- https://documentation.wazuh.com/current/user-manual/ruleset/decoders/sibling-decoders.html
- https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/decoders.html
- https://documentation.wazuh.com/current/user-manual/ruleset/decoders/index.html
- https://wazuh.com/blog/sibling-decoders-flexible-extraction-of-information/
- https://github.com/wazuh/wazuh/issues/7310
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/C0A933R8E/p1770986661732759