· 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.

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

Decoder 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-logtest

Eingabe:

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-IP
  • host-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 hat
  • server — nur auf dem Manager-Host
  • all — 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\"}"
fi

Das 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.

Zum Blog

Ähnliche Artikel

Alle Artikel »