Fallback-Regeln statt Fallback-Decoder: Unbekannte Syslog-Typen und Felder zuverlässig in Wazuh erkennen

Einleitung

Bei Firewalls und Security-Appliances ist Syslog oft die einzige Telemetriequelle – und gleichzeitig die unbequemste: viele Module, wechselnde Felder, keine Herstellerdokumentation. Für Wazuh-Administratoren entsteht damit ein doppeltes Problem: Einerseits will man saubere Decoder und sinnvolle Regeln für bekannte Message-Typen, andererseits möchte man sofort alarmiert werden, wenn neue Message-Typen oder neue Felder auftauchen, um den Parser und die Use-Cases iterativ zu erweitern.

Dieser Beitrag zeigt eine robuste Vorgehensweise für genau dieses Szenario – ohne dass ein „Wildcard-Decoder“ alle anderen Decoder überfährt. Der Schlüssel ist: Fallback passiert in den Rules, nicht in den Decodern.


Ausgangslage / Problemstellung

  • Eine Firewall sendet Syslog an Wazuh (über Load Balancer/HAProxy).
  • Es existieren bereits mehrere Custom-Decoder je Message-Typ, jeweils mit Child-/Sibling-Decodern für Felder.
  • Der Hersteller dokumentiert die möglichen Syslog-Typen nicht.
  • Ziel:
    1. Unbekannte Message-Typen (neue Module/Events) sollen zuverlässig mit hoher Severity alarmieren.
    2. Decoder sollen für bekannte Typen weiterhin präzise bleiben (kein Wildcard-Decoder als „Vorfahrt“-Problem).
    3. Perspektivisch: Auch unerwartete Felder innerhalb bekannter Typen erkennen.

Logformat (vereinfacht):
<timestamp> <host> <program>[<id>]: <msgtype> <field>=<value> <field>=<value> ...


Technische Analyse

1) Warum „Fallback-Decoder“ in Wazuh meist scheitert

Wazuh-Decoder-Matching ist deterministisch: Ein zu generischer Decoder (z. B. Regex .*) kann Decoder für spezifische Message-Typen verdrängen. Gleichzeitig erlaubt Wazuh im Decoder-Design kein beliebig tiefes „Multi-Level-Parenting“, wie man es aus Parser-Frameworks kennt. Dadurch kollidiert die Idee „Basisdecoder → Message-Type-Decoder → Field-Decoder“ in der Praxis schnell mit den Möglichkeiten des Rulesets.

2) Der saubere Ansatz: Ein einziger Basisdecoder + viele Sibling-Field-Decoder

Statt pro Message-Typ einen Parent-Decoder zu bauen, definiert man:

  • einen Parent-Decoder, der nur auf program_name matcht (z. B. nbsyslog)
  • einen Sibling-Decoder, der den Message-Typ als erstes Token extrahiert
  • weitere Sibling-Decoder, die einzelne Felder unabhängig von Reihenfolge matchen

Damit kann Wazuh:

  • jede Zeile mit diesem Programm dekodieren
  • Felder in beliebiger Reihenfolge extrahieren (sibling decoders)
  • und der Message-Typ steht als Feld (msgtype) zur Verfügung

3) Fallback wird im Rule-Set gelöst (nicht beim Decoding)

Die „Unbekannt“-Erkennung passiert dann durch eine Fallback-Rule, die auf den Basisdecoder reagiert. Spezifische Regeln für bekannte Typen hängen sich darunter (via <if_sid>), sodass nur wirklich unbekannte Typen in der Fallback-Rule „übrig bleiben“.


Lösung / Best Practices

1) Decoder-Design: program_name als Parent, msgtype + Felder als Siblings

Beispiel basierend auf dem beschriebenen nbsyslog-Programm und den Firewall/System/VPN-Logs:

Decoder-Datei (z. B. /var/ossec/etc/decoders/nbsyslog_decoders.xml):

<!-- Basisdecoder: nur program_name -->
<decoder name="nbsyslog_decoder">
  <program_name>nbsyslog</program_name>
</decoder>

<!-- Message-Type extrahieren: erstes Token nach dem Syslog-Header -->
<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex>^(\S+)</regex>
  <order>msgtype</order>
</decoder>

<!-- Beispiel-Felder: Firewall -->
<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex> firewall.result=(\S+)</regex>
  <order>fw_result</order>
</decoder>

<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex> firewall.mac.dst=(\S+)</regex>
  <order>fw_mac_dst</order>
</decoder>

<!-- Beispiel-Felder: System -->
<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex> cpu\.steal=(\S+)</regex>
  <order>sys_cpu_steal</order>
</decoder>

<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex> period\.utc=(\".*?\"|\S+)</regex>
  <order>sys_period_utc</order>
</decoder>

<!-- Beispiel-Felder: VPN -->
<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex> application=(\S+)</regex>
  <order>vpn_application</order>
</decoder>

<decoder name="nbsyslog_fields">
  <parent>nbsyslog_decoder</parent>
  <regex>:\d+\.remote_port=(\S+)</regex>
  <order>vpn_remote_port</order>
</decoder>

Warum das funktioniert:

  • Kein Decoder „frisst“ die anderen, weil alle Field-Decoder am selben Parent hängen.
  • Reihenfolge ist egal, weil jeder Field-Decoder gezielt ein Feld sucht.
  • msgtype ist immer vorhanden und kann in Rules als Switch genutzt werden.

2) Rule-Design: Fallback-Rule für Unbekanntes, spezifische Rules für Bekanntes

Rules-Datei (z. B. /var/ossec/etc/rules/nbsyslog_rules.xml):

<group name="syslog,nbsyslog,">

  <!-- Fallback: Alles, was durch den Basisdecoder geht -->
  <rule id="110600" level="12">
    <decoded_as>nbsyslog_decoder</decoded_as>
    <description>nbsyslog: Unbekannter Message-Typ oder nicht abgedeckte Logvariante. msgtype=$(msgtype)</description>
  </rule>

  <!-- Bekannter Typ: Firewall deny -->
  <rule id="110611" level="5">
    <if_sid>110600</if_sid>
    <field name="msgtype">firewall</field>
    <field name="fw_result">deny</field>
    <description>Firewall: Traffic denied. DST MAC: $(fw_mac_dst)</description>
  </rule>

  <!-- Bekannter Typ: VPN SSL server connections -->
  <rule id="110631" level="5">
    <if_sid>110600</if_sid>
    <field name="msgtype">vpn.ssl.server.connections</field>
    <field name="vpn_application">ssl.server</field>
    <description>VPN SSL Server Connection aktiv. Remote-Port: $(vpn_remote_port)</description>
  </rule>

</group>

Effekt:

  • Alles landet zunächst in Rule 110600.
  • Für bekannte Message-Typen greifen spezifische Sub-Rules (und man kann Level, Groups, Notifications getrennt steuern).
  • Wenn ein neuer msgtype auftaucht oder ein bekannter Typ keine passenden Felder liefert, bleibt die Fallback-Rule aktiv und alarmiert mit hoher Severity.

3) „Unbekannte Felder“ automatisch erkennen: realistische Grenzen und pragmatische Wege

Bei nicht-JSON-Logs ist Wazuh nicht in der Lage, „alle Key=Value-Paare dynamisch“ als Felder zu materialisieren. Das bedeutet:

  • Neue Felder werden nicht „automatisch“ sichtbar als strukturierte Fields.
  • Du kannst nur Felder matchen, die du explizit mit Regex decodierst.

Was trotzdem praktikabel ist:

A) Iteratives Vorgehen (bewährt in Produktion)

  • Fallback-Rule (Level 12) sorgt dafür, dass neue Varianten auffallen.
  • Aus den Fallback-Alerts nimmt man Beispielzeilen und ergänzt gezielt weitere Sibling-Decoder für neue Felder.

B) Normalisierung in JSON vor Wazuh (wenn möglich)
Wenn du den Traffic ohnehin über einen Load Balancer/Proxy (z. B. HAProxy) führst, ist der Gedanke valide: vor dem Manager in JSON transformieren. Dann kann Wazuhs JSON-Decoder dynamisch Felder übernehmen, ohne dass jeder Key als Regex gepflegt werden muss. Realistisch umzusetzen ist das typischerweise nicht direkt „in HAProxy“, sondern über einen vorgeschalteten Parser (z. B. syslog pipeline), der:

  • Syslog annimmt
  • Key=Value extrahiert
  • JSON schreibt/forwardet

Das ist mehr Engineering, reduziert aber langfristig Decoder-Pflege drastisch – besonders bei Appliances ohne Dokumentation.


Lessons Learned / Best Practices

  1. Fallback gehört ins Rule-Set, nicht in generische Decoder
    Wildcard-Decoder sind bequem, aber riskant. Eine Fallback-Rule ist kontrollierbar und kollidiert nicht mit spezifischen Decodern.
  2. Message-Type als Feld extrahieren, dann per Rules routen
    Sobald msgtype als Field existiert, wird das Regelwerk zum „Router“ für Known/Unknown – und bleibt erweiterbar.
  3. Sibling-Decoder sind das Mittel gegen Feldreihenfolge-Chaos
    Wenn Felder in wechselnder Reihenfolge erscheinen, ist ein pro Feld separater Sibling-Decoder stabiler als ein großer Regex.
  4. Unbekannte Felder sind ohne JSON ein Pflege-Thema
    Ohne JSON bleibt nur iteratives Ergänzen. Wer das vermeiden will, sollte über JSON-Normalisierung in der Pipeline nachdenken.
  5. Hohe Severity nur für wirklich „unbekannt“ – sonst Alert-Fatigue
    Die Fallback-Rule ist mächtig. In der Praxis lohnt es sich, sie zunächst high zu setzen, später aber bekannte, harmlose Typen gezielt herunterzustufen.

Fazit

Wenn Firewalls Syslogs ohne Dokumentation liefern, ist „vollständiges“ Decoding ein Prozess – kein Einmalprojekt. Die zuverlässigste Lösung in Wazuh ist ein einziger program_name-Decoder, ergänzt um Sibling-Decoder für msgtype und relevante Felder. Die eigentliche Erkennung unbekannter Typen erfolgt über eine Fallback-Rule mit hoher Severity, während bekannte Typen durch spezifische Sub-Rules sauber klassifiziert werden. Für das automatische Erfassen unbekannter Felder führt langfristig kaum ein Weg an einer JSON-Normalisierung vor Wazuh vorbei – bis dahin ist das Fallback-Rule-Pattern der stabilste Hebel für Sichtbarkeit und kontinuierliche Verbesserung.

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/p1769167384731989