· Henning Scholand · Security · 5 Min. Lesezeit
Wazuh — Eigene Decoder, Rules und Active Response
Standard-Regelwerk deckt vieles ab — aber die eigene Umgebung kennt Wazuh nicht. Wer Decoder und Rules schreibt, schärft das Sensorium gezielt.

Das Standard-Regelwerk von Wazuh ist beeindruckend breit: SSH-Brute-Force, Sudo-Eskalation, bekannte Exploit-Muster, Windows-Event-IDs — tausende Rules, gepflegt von der Community. Aber es gibt eine Lücke: eigene Anwendungen. Wazuh kennt nicht, was deine selbst geschriebene API loggt, wie dein internes Tool Fehler schreibt oder was ein Deployment-Script meldet.
Dieser Artikel zeigt, wie Decoder und Rules für eigene Log-Formate entstehen — und wie Active Response dann automatisch reagiert.
Wie Wazuh Logs verarbeitet
Bevor wir schreiben, müssen wir verstehen, was intern passiert. Wazuh verarbeitet jeden eingehenden Log durch vier Phasen:
Pre-Decoding extrahiert aus dem Syslog-Header automatisch Hostname, Timestamp und Programm-Name. Diese Phase läuft ohne Konfiguration.
Decoding extrahiert strukturierte Felder aus dem Nachrichtenteil. Hier kommen eigene Decoder ins Spiel. Ein Decoder liest den Log und mappt Teile davon auf benannte Felder — srcip, user, action und andere.
Rule Matching prüft jeden dekodierter Event gegen das Regelwerk. Die erste passende Rule gewinnt — außer bei if_matched_sid-Rules (Frequenzauswertung) und kombinierten Regeln, die auf frühere Matches verweisen.
Alerting schreibt den Alert in den Indexer und löst ggf. Active Response aus.
Rohlog → Pre-Decoding → Decoding → Rule Matching → Alert/ARDecoder schreiben
Decoder liegen unter /var/ossec/etc/decoders/local_decoder.xml. Diese Datei ist für eigene Decoder reserviert — das mitgelieferte Regelwerk landet in /var/ossec/ruleset/decoders/ und wird bei Updates überschrieben.
Ein Decoder hat immer eine Eltern-Kind-Struktur. Der Eltern-Decoder identifiziert das Log-Format, der Kind-Decoder extrahiert die Felder.
<!-- /var/ossec/etc/decoders/local_decoder.xml -->
<decoder name="myapp">
<prematch>^myapp\[\d+\]:</prematch>
</decoder>
<decoder name="myapp-login">
<parent>myapp</parent>
<regex>user='(\S+)' action='(\S+)' ip='(\d+\.\d+\.\d+\.\d+)'</regex>
<order>user, action, srcip</order>
</decoder><prematch> ist ein verankerter Regex — er wird am Anfang des Nachrichtenteils geprüft. Nur wenn er matcht, wird dieser Decoder und seine Kinder überhaupt angewendet. Das macht die Verarbeitung effizient.
<parent> verknüpft Kind- mit Eltern-Decoder. Ein Kind wird nur ausgewertet, wenn der Eltern-Decoder bereits gegriffen hat.
<regex> extrahiert Felder per Capture-Groups. Die Reihenfolge der Groups entspricht der Reihenfolge in <order>.
<order> benennt die extrahierten Felder. Reservierte Feldnamen — srcip, dstip, srcport, dstport, user, url, id, status — werden von Wazuh direkt verstanden und in der Alert-Ansicht besonders behandelt.
Ein Beispiel-Log, das dieser Decoder verarbeitet:
Oct 31 09:14:23 webserver myapp[1234]: user='admin' action='failed' ip='10.0.0.1'Decoder testen mit wazuh-logtest
Bevor eine Rule geschrieben wird, immer zuerst den Decoder testen. Das Tool /var/ossec/bin/wazuh-logtest ist interaktiv — Log eingeben, Ergebnis sofort sehen:
/var/ossec/bin/wazuh-logtestEingabe:
Oct 31 09:14:23 webserver myapp[1234]: user='admin' action='failed' ip='10.0.0.1'Erwartete Ausgabe:
**Phase 1: Completed pre-decoding.
full event: 'Oct 31 09:14:23 webserver myapp[1234]: user=...'
hostname: 'webserver'
program_name: 'myapp'
**Phase 2: Completed decoding.
decoder: 'myapp-login'
user: 'admin'
action: 'failed'
srcip: '10.0.0.1'
**Phase 3: Completed filtering (rules).
No rules matched.Phase 2 zeigt: Decoder greift, Felder sind korrekt extrahiert. Erst jetzt kommt die Rule.
Rules schreiben
Rules liegen unter /var/ossec/etc/rules/local_rules.xml. Die ID-Vergabe ist geregelt: 100001–199999 sind für eigene Rules reserviert. Wazuh selbst nutzt 0–99999.
Das Level bestimmt die Schwere: 0 ignoriert, 1–3 Info, 4–6 niedrig, 7–11 mittel, 12–15 kritisch. Alerts ab Level 7 erscheinen standardmäßig im Dashboard.
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="myapp,authentication,">
<rule id="100001" level="5">
<decoded_as>myapp-login</decoded_as>
<field name="action">failed</field>
<description>myapp: Fehlgeschlagener Login von $(srcip)</description>
<group>authentication_failed,</group>
<mitre>
<id>T1110</id>
</mitre>
</rule>
<rule id="100002" level="10" frequency="5" timeframe="120">
<if_matched_sid>100001</if_matched_sid>
<same_field>srcip</same_field>
<description>myapp: Brute-Force von $(srcip) — 5 Fehlversuche in 2 Minuten</description>
<group>authentication_failures,</group>
<mitre>
<id>T1110</id>
</mitre>
</rule>
</group><decoded_as> — Rule greift nur auf Events, die durch diesen Decoder liefen.
<field name="action"> — prüft ein dekodiertes Feld. Hier: Rule feuert nur, wenn action den Wert failed hat.
<description> — $(srcip) wird durch den tatsächlichen Feldwert ersetzt, erscheint so im Alert.
<mitre><id> — verknüpft den Alert mit einer ATT&CK-Technik. Wird im Dashboard in der MITRE-Matrix angezeigt.
frequency + timeframe — Rule 100002 ist eine Frequenz-Rule: sie feuert, wenn innerhalb von 120 Sekunden 5 Mal Rule 100001 mit derselben Quell-IP ausgelöst wurde. Das ist Brute-Force-Erkennung ohne externe Tools.
Rule testen
Zurück zu wazuh-logtest. Jetzt sollte Phase 3 eine Regel zeigen:
**Phase 3: Completed filtering (rules).
Rule id: '100001' level '5' -> 'myapp: Fehlgeschlagener Login von 10.0.0.1'Für die Frequenz-Rule: fünf identische Log-Zeilen nacheinander eingeben. Nach dem fünften Match springt die Rule auf 100002.
Active Response
Active Response führt automatisch Aktionen aus, wenn eine Rule feuert. Die Konfiguration liegt im <ossec_config>-Block von /var/ossec/etc/ossec.conf auf dem Manager.
Eingebaute Commands:
firewall-drop— setzt eine iptables-DROP-Regel für die Quell-IPhost-deny— schreibt die IP in/etc/hosts.deny
<!-- /var/ossec/etc/ossec.conf — im <ossec_config>-Block -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100002</rules_id>
<timeout>3600</timeout>
</active-response><location> bestimmt, wo die Aktion ausgeführt wird:
local— auf dem Agent, der den Event gemeldet hatserver— nur auf dem Manager-Hostall— auf allen registrierten Agents
<timeout> gibt an, nach wie vielen Sekunden die Aktion rückgängig gemacht wird. 0 bedeutet dauerhaft. Für firewall-drop wird die DROP-Regel nach 3600 Sekunden (1 Stunde) wieder entfernt.
Eigene Active-Response-Scripts
Eigene Scripts kommen nach /var/ossec/active-response/bin/. Sie müssen ausführbar sein und erhalten den Event-Kontext als JSON über stdin. Ein minimales Bash-Script:
#!/bin/bash
# /var/ossec/active-response/bin/notify-slack.sh
read -r INPUT
ACTION=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('command',''))")
if [ "$ACTION" = "add" ]; then
ALERT=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('alert',{}).get('full_log',''))")
curl -s -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"text\": \"Wazuh Alert: $ALERT\"}"
fiDas Script zuerst als Command registrieren:
<command>
<name>notify-slack</name>
<executable>notify-slack.sh</executable>
<extra_args></extra_args>
<timeout_allowed>no</timeout_allowed>
</command>MITRE ATT&CK im Dashboard
Jede Rule, die <mitre><id> trägt, taucht im Dashboard unter Threat Intelligence → MITRE ATT&CK auf. Die Matrix zeigt, welche Techniken in der eigenen Umgebung bereits ausgelöst haben — farblich nach Häufigkeit gewichtet.
Das ist kein kosmetisches Feature: es hilft zu sehen, welche Taktiken (Initial Access, Persistence, Lateral Movement) bereits Signale erzeugen und wo noch blinde Flecken sind.
Nächster Schritt
Host-Daten und eigene Anwendungen sind abgedeckt. Was noch fehlt: das Netzwerk. Im nächsten Artikel zeige ich, wie Suricata als Netzwerk-IDS mit Wazuh integriert wird — und wie Host-Events und Netzwerk-Alerts korreliert werden.



