Wazuh: SentinelOne-CEF-Daten aus event.original in JSON-Logs dekodieren

Einleitung

Security-Produkte und Log-Pipelines verpacken Ereignisse häufig mehrfach. Ein typisches Beispiel ist ein JSON-Dokument, das in einem seiner Felder wiederum eine Syslog-, CEF- oder Key-Value-Nachricht enthält. Für Wazuh entsteht dadurch eine zweistufige Struktur: Der integrierte JSON-Decoder verarbeitet zwar das äußere Dokument, interpretiert den Inhalt eines String-Feldes jedoch nicht automatisch als weiteres Logformat.

Bei SentinelOne-Ereignissen kann das dazu führen, dass wichtige Informationen wie Dateiname, Schweregrad, Mitigationsstatus oder Kommandozeile ausschließlich innerhalb von event.original verbleiben. Diese Werte stehen anschließend nicht als eigenständige Felder für Regeln, Dashboards oder Korrelationen zur Verfügung.

Mit einer Kombination aus dem standardmäßigen JSON-Decoder und modularen Sibling-Decodern lassen sich die eingebetteten Felder dennoch zuverlässig extrahieren.

Ausgangslage / Problemstellung

Das eingehende Ereignis besteht aus einem regulären JSON-Dokument. In vereinfachter Form sieht die Struktur folgendermaßen aus:

{
  "client": "CLIENT_NAME",
  "@version": "1",
  "type": "sentinelone-test-syslog-udp",
  "@timestamp": "2025-12-31T14:55:22.476337335Z",
  "event": {
    "original": "<12>2025-12-31 14:55:22,416 sentinel - CEF:2|SentinelOne|Mgmt|Windows Server 2022 Datacenter|osName=Windows Server 2022 Datacenter|eventSeverity=10|sourceHostName=HOST-REDACTED|threatMitigationStatus=not_mitigated|threatConfidenceLevel=malicious|threatCommandLineArguments=...|threatID=4444444444444444444|fileName=Unknown file|activityType=19"
  }
}

Der standardmäßige Wazuh-JSON-Decoder erkennt unter anderem folgende Felder:

client
@version
type
@timestamp
event.original

Der komplette SentinelOne-Inhalt bleibt jedoch als String im Feld event.original gespeichert. Werte wie diese sind zunächst nicht separat ansprechbar:

fileName
osName
eventSeverity
sourceHostName
threatConfidenceLevel
threatMitigationStatus
threatCommandLineArguments

Ein naheliegender erster Versuch ist ein Child-Decoder unterhalb des JSON-Decoders:

<decoder name="custom-sentinelone">
  <parent>json</parent>
  <prematch type="pcre2">"type":"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">fileName=(\w+)</regex>
  <order>file_name</order>
</decoder>

Diese Konfiguration hat mehrere Schwachstellen:

  1. Der JSON-Plugin-Decoder und die zusätzlichen regulären Ausdrücke müssen als geeignete Decoder-Zweige kombiniert werden.
  2. \w+ erfasst in PCRE2 nur Wortzeichen. Der Wert Unknown file enthält ein Leerzeichen und wird daher nicht vollständig extrahiert.
  3. Die eingebetteten Felder sind durch Pipe-Zeichen getrennt. Der reguläre Ausdruck sollte diese Feldgrenzen berücksichtigen.
  4. Der Decoder sollte ausschließlich auf den vorgesehenen SentinelOne-Logtyp reagieren.
  5. Das Hinzufügen spezialisierter Child-Decoder darf die Verarbeitung anderer JSON-Ereignisse nicht beeinträchtigen.

Der Wazuh-JSON-Decoder legt Werte des JSON-Dokuments als dynamische Felder an. Ein String bleibt dabei ein String, auch wenn sein Inhalt wie Syslog oder CEF aussieht. Die Wazuh-Dokumentation beschreibt außerdem die Kombination eines Plugin-Decoders mit zusätzlichen regulären Ausdrücken über mehrere Decoder auf derselben Hierarchieebene.

Technische Analyse

Keine rekursive Dekodierung von event.original

Der JSON_Decoder verarbeitet die Syntax des äußeren JSON-Dokuments. Er erkennt Objekte, Strings, Zahlen, boolesche Werte, Arrays und Nullwerte und legt sie als dynamische Felder ab.

Der Wert von event.original ist aus Sicht des JSON-Decoders lediglich eine Zeichenkette:

"<12>2025-12-31 ... CEF:2|SentinelOne|Mgmt|..."

Wazuh startet für diesen Feldwert nicht automatisch einen zweiten Decoder-Durchlauf. Ein Child-Decoder arbeitet außerdem nicht direkt auf einem bereits dekodierten dynamischen Feld. Seine regulären Ausdrücke werden weiterhin auf den ursprünglichen Logeintrag angewendet.

Da der vollständige Inhalt von event.original im ursprünglichen JSON-Log enthalten ist, kann er trotzdem mit regulären Ausdrücken extrahiert werden. Dabei müssen allerdings die JSON-Escapes und die Struktur der eingebetteten CEF-Nachricht berücksichtigt werden.

Parent-Child- und Sibling-Logik

Nachdem ein Top-Level-Decoder ein Ereignis akzeptiert hat, konzentriert sich Wazuh auf dessen Child-Decoder. Mehrere Decoder mit demselben Namen und demselben Parent können modular jeweils einzelne Felder extrahieren. Dieses Verfahren wird als Sibling-Decoder-Strategie verwendet.

Es eignet sich besonders für Key-Value-Logs, bei denen:

  • Felder fehlen können,
  • sich die Reihenfolge ändern kann,
  • zusätzliche Felder eingefügt werden,
  • nicht jedes Ereignis dieselbe Struktur besitzt.

Statt eines einzigen sehr langen regulären Ausdrucks wird für jedes relevante Feld ein eigener Decoder definiert. Dadurch bleibt die Verarbeitung stabil, selbst wenn SentinelOne die Reihenfolge einzelner Extension-Felder verändert.

Wichtig ist außerdem, dass Wazuh keine beliebig tiefen Decoder-Bäume unterstützt. Ein Child-Decoder kann nicht wiederum als Parent für eine weitere Ebene verwendet werden. Alle Extraktionsdecoder sollten daher direkt unterhalb des vorhandenen json-Parents angeordnet werden.

Unterschied zwischen OSRegex und PCRE2

Wazuh unterstützt mehrere Regex-Varianten. Dabei unterscheiden sich insbesondere OSRegex und PCRE2 deutlich.

In OSRegex steht:

\.

für ein beliebiges Zeichen. Deshalb findet man in älteren oder communitybasierten Decoder-Beispielen häufig Konstruktionen wie:

<regex>fileName=(\.+)\|</regex>

In PCRE2 bedeutet \. dagegen ein literales Punktzeichen. Der gleiche Ausdruck würde dort ausschließlich eine Folge von Punkten erfassen.

Für neue Decoder ist eine explizite PCRE2-Zeichenklasse meist verständlicher:

([^|]*)

Sie bedeutet: Erfasse beliebig viele Zeichen, solange kein Pipe-Zeichen erreicht wird.

Für den Beispielwert ergibt sich damit:

fileName=Unknown file|
             └────────┘
             erfasster Wert

Die unterschiedlichen Regex-Dialekte sollten nicht innerhalb einer Konfiguration gedanklich vermischt werden.

Warum der zusätzliche JSON_Decoder-Zweig relevant ist

Der standardmäßige Decoder json verwendet bereits den Plugin-Decoder JSON_Decoder. Beim Erweitern dieses Decoder-Zweigs um spezialisierte Child- beziehungsweise Sibling-Decoder sollte dennoch ein generischer Plugin-Zweig erhalten bleiben:

<decoder name="json_custom">
  <parent>json</parent>
  <plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>

Dieser Eintrag ist kein zweites fachliches Parsing der eingebetteten CEF-Nachricht. Er bildet den allgemeinen JSON-Zweig innerhalb der erweiterten Decoder-Struktur.

Die SentinelOne-spezifischen Regex-Decoder treffen nur auf Ereignisse zu, die das passende type-Feld und das jeweilige CEF-Feld enthalten. Der generische Plugin-Zweig verhindert, dass die Erweiterung ausschließlich für passende SentinelOne-Ereignisse funktioniert und andere JSON-Logs innerhalb derselben Decoder-Struktur nicht mehr wie erwartet verarbeitet werden.

Die offizielle Wazuh-Dokumentation zeigt ebenfalls, wie ein JSON-Plugin-Decoder und reguläre Ausdrücke über mehrere Child-Decoder desselben Parents kombiniert werden.

Lösung / Best Practices

Decoder-Datei anlegen

Die benutzerdefinierten Decoder sollten nicht in den mitgelieferten Dateien unterhalb von /var/ossec/ruleset/decoders/ ergänzt werden. Änderungen in diesem Verzeichnis können bei einem Update überschrieben werden.

Eine geeignete Datei wäre beispielsweise:

/var/ossec/etc/decoders/sentinelone_json_decoders.xml

Wazuh empfiehlt, eigene Decoder in /var/ossec/etc/decoders/ beziehungsweise für kleinere Anpassungen in local_decoder.xml abzulegen.

Vollständige Decoder-Konfiguration

Die folgende Konfiguration erhält zunächst die allgemeine JSON-Verarbeitung und extrahiert anschließend ausgewählte SentinelOne-Felder aus dem eingebetteten String:

<!-- Generischer JSON-Zweig -->
<decoder name="json_custom">
  <parent>json</parent>
  <plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>

<!-- SentinelOne: Dateiname -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|fileName=([^|]*)\|</regex>
  <order>sentinelone_file_name</order>
</decoder>

<!-- SentinelOne: Betriebssystem -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|osName=([^|]*)\|</regex>
  <order>sentinelone_os_name</order>
</decoder>

<!-- SentinelOne: Ereignisschweregrad -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|eventSeverity=(\d+)\|</regex>
  <order>sentinelone_event_severity</order>
</decoder>

<!-- SentinelOne: Quellhostname -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|sourceHostName=([^|]*)\|</regex>
  <order>sentinelone_source_hostname</order>
</decoder>

<!-- SentinelOne: Threat Confidence -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|threatConfidenceLevel=([^|]*)\|</regex>
  <order>sentinelone_threat_confidence</order>
</decoder>

<!-- SentinelOne: Mitigationsstatus -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|threatMitigationStatus=([^|]*)\|</regex>
  <order>sentinelone_mitigation_status</order>
</decoder>

<!-- SentinelOne: Kommandozeile -->
<decoder name="json_custom">
  <parent>json</parent>
  <prematch type="pcre2">"type"\s*:\s*"sentinelone-test-syslog-udp"</prematch>
  <regex type="pcre2">\|threatCommandLineArguments=(.*?)\|threatID=</regex>
  <order>sentinelone_command_line</order>
</decoder>

Alle Extraktionsdecoder besitzen denselben Namen und denselben Parent. Dadurch behandelt Wazuh sie als modulare Decoder derselben Gruppe.

Warum die Kommandozeile anders extrahiert wird

Für einfache Werte reicht eine Pipe als Feldbegrenzung:

\|fileName=([^|]*)\|

Eine Kommandozeile kann jedoch selbst Sonderzeichen oder Pipe-Zeichen enthalten. Deshalb ist für threatCommandLineArguments eine Begrenzung über den nachfolgenden, stabilen Feldnamen robuster:

\|threatCommandLineArguments=(.*?)\|threatID=

Der Ausdruck erfasst den Inhalt nicht-gierig bis zum nächsten bekannten Schlüssel threatID.

Diese Methode setzt voraus, dass threatID in den betreffenden Ereignissen unmittelbar nach threatCommandLineArguments folgt. Falls SentinelOne die Reihenfolge dieser beiden Felder ändern kann, sollte die Kommandozeile entweder nicht vollständig extrahiert oder bereits in der vorgelagerten Log-Pipeline strukturiert werden.

Decoder mit wazuh-logtest prüfen

Nach dem Speichern der Datei sollte das Beispielereignis mit wazuh-logtest geprüft werden:

sudo /var/ossec/bin/wazuh-logtest

Das vollständige JSON-Ereignis muss als einzelne Zeile eingefügt werden.

In Phase 2 sollten unter anderem folgende Werte erscheinen:

name: 'json'
type: 'sentinelone-test-syslog-udp'
sentinelone_file_name: 'Unknown file'
sentinelone_os_name: 'Windows Server 2022 Datacenter'
sentinelone_event_severity: '10'
sentinelone_source_hostname: 'HOST-REDACTED'
sentinelone_threat_confidence: 'malicious'
sentinelone_mitigation_status: 'not_mitigated'

Bei produktiven Logs können Backslashes und Anführungszeichen der Kommandozeile in der Ausgabe weiterhin escaped dargestellt werden. Entscheidend ist, dass der Inhalt vollständig im Feld sentinelone_command_line ankommt.

Nach erfolgreichem Test muss der Wazuh Manager neu gestartet werden, damit die Decoder für die reguläre Ereignisverarbeitung verwendet werden:

sudo systemctl restart wazuh-manager

Die Wazuh-Dokumentation empfiehlt wazuh-logtest ausdrücklich zum Testen eigener Decoder und weist darauf hin, dass der Manager für die produktive Verarbeitung nach Änderungen neu gestartet werden muss.

Passende Regel mit decoded_as

Ohne die Option use_own_name verwendet ein Child-Decoder standardmäßig den Namen seines Parent-Decoders. In diesem Fall zeigt wazuh-logtest deshalb weiterhin:

name: 'json'

Die zugehörige Regel muss dementsprechend Folgendes enthalten:

<decoded_as>json</decoded_as>

Nicht korrekt wäre in dieser Konfiguration:

<decoded_as>json_custom</decoded_as>

Mit use_own_name könnte ein Child-Decoder ausdrücklich seinen eigenen Namen verwenden. Ohne diese Option bleibt jedoch der Parent-Name maßgeblich.

Eine passende Gruppierungsregel kann folgendermaßen aussehen:

<group name="sentinelone,">

  <rule id="100500" level="0">
    <decoded_as>json</decoded_as>
    <field name="type" type="pcre2">^sentinelone-test-syslog-udp$</field>
    <description>SentinelOne-Ereignis wurde als JSON empfangen.</description>
  </rule>

  <rule id="100501" level="12">
    <if_sid>100500</if_sid>
    <field name="sentinelone_event_severity" type="pcre2">^(?:9|10)$</field>
    <field name="sentinelone_threat_confidence" type="pcre2">^malicious$</field>
    <field name="sentinelone_mitigation_status" type="pcre2">^not_mitigated$</field>
    <description>SentinelOne meldet eine aktive, nicht mitigierte Bedrohung.</description>
    <group>malware,endpoint_detection_response,</group>
  </rule>

</group>

Die erste Regel grenzt die Ereignisquelle über das bereits vom JSON-Decoder extrahierte Feld type ein. Die zweite Regel bewertet ausschließlich sicherheitsrelevante SentinelOne-Felder.

Dadurch reicht decoded_as=json nicht allein als Erkennungsmerkmal aus. Andernfalls könnte die Regel auf sämtliche JSON-Quellen im Wazuh-System angewendet werden.

Lessons Learned / Best Practices

Decoder und Regeln getrennt betrachten

Decoder extrahieren und normalisieren Informationen. Regeln bewerten diese Informationen.

Die Entscheidung, ob ein Ereignis kritisch ist, sollte nicht durch einen übermäßig restriktiven Decoder getroffen werden. Der Decoder sollte relevante Werte möglichst neutral erfassen. Erst die Regel entscheidet beispielsweise, ob folgende Kombination einen Alert auslösen soll:

eventSeverity = 10
threatConfidenceLevel = malicious
threatMitigationStatus = not_mitigated

Quellen frühzeitig eingrenzen

Die eingebetteten Feldnamen sind nicht zwangsläufig weltweit eindeutig. Ein JSON-Ereignis aus einer anderen Anwendung könnte ebenfalls einen Text wie fileName= enthalten.

Jeder spezialisierte Decoder sollte deshalb über einen stabilen Quellenindikator eingeschränkt werden:

<prematch type="pcre2">
  "type"\s*:\s*"sentinelone-test-syslog-udp"
</prematch>

Zusätzlich sollte die Regel dasselbe type-Feld prüfen.

Feldgrenzen statt \w+ verwenden

Werte aus Security-Produkten enthalten regelmäßig:

  • Leerzeichen,
  • Backslashes,
  • Doppelpunkte,
  • Bindestriche,
  • Punkte,
  • UNC-Pfade,
  • Benutzer- und Domänennamen.

Ein Ausdruck wie \w+ ist daher für die meisten Felder zu restriktiv.

Für Pipe-separierte Werte ist dieses Muster besser geeignet:

([^|]*)

Bei Feldern, die selbst Pipes enthalten können, sollte bis zu einem stabilen Folgeschlüssel extrahiert werden.

Nur tatsächlich benötigte Felder indexieren

SentinelOne-Ereignisse können sehr große Werte enthalten, insbesondere:

  • Base64-kodierte PowerShell-Kommandos,
  • vollständige Kommandozeilen,
  • Indicator-Listen,
  • Prozessketten,
  • Dateipfade,
  • Netzwerkadresslisten.

Eine vollständige Extraktion aller Werte erhöht:

  • die Größe der Wazuh-Alerts,
  • das Indexvolumen,
  • den Speicherbedarf,
  • die Komplexität von Index-Mappings,
  • das Risiko, sensitive Befehls- oder Benutzerdaten unnötig zu speichern.

Für Regeln sollten vorrangig stabile und sicherheitsrelevante Felder extrahiert werden. Der vollständige Rohinhalt bleibt weiterhin in event.original erhalten und kann bei einer Untersuchung herangezogen werden.

Positive und negative Tests verwenden

Ein Decoder-Test sollte mindestens drei Fälle umfassen:

  1. Ein passendes SentinelOne-Ereignis mit allen erwarteten Feldern.
  2. Ein SentinelOne-Ereignis, in dem einzelne Felder fehlen.
  3. Ein beliebiges anderes JSON-Ereignis.

Beim dritten Test muss weiterhin die normale JSON-Dekodierung funktionieren. Gleichzeitig dürfen keine sentinelone_*-Felder erzeugt werden.

Änderungen nicht im Standard-Ruleset vornehmen

Die mitgelieferte JSON-Decoder-Datei sollte nicht direkt bearbeitet werden. Eigene Dateien unter /var/ossec/etc/decoders/ sind updatebeständiger und lassen sich getrennt versionieren, testen und verteilen.

In Cluster-Umgebungen sollte außerdem sichergestellt werden, dass Decoder- und Regeldateien konsistent auf den relevanten Manager-Knoten vorhanden sind.

decoded_as immer anhand von wazuh-logtest bestimmen

Der Name im XML-Element <decoder name="..."> ist nicht in jeder Parent-Child-Konstellation automatisch der Wert, den Regeln über decoded_as sehen.

Maßgeblich ist die Ausgabe von Phase 2:

name: 'json'

Daher lautet die richtige Bedingung in diesem Fall:

<decoded_as>json</decoded_as>

Wird später bewusst use_own_name aktiviert, muss diese Entscheidung erneut mit wazuh-logtest validiert werden.

Fazit

Ein JSON-Feld mit eingebettetem Syslog- oder CEF-Inhalt wird vom Wazuh-JSON-Decoder nicht automatisch rekursiv dekodiert. Der äußere JSON-Datensatz wird korrekt strukturiert, während event.original zunächst ein einfacher String bleibt.

Die zuverlässige Lösung besteht aus einem generischen JSON_Decoder-Zweig und mehreren SentinelOne-spezifischen Sibling-Decodern unterhalb des bestehenden json-Parents. Jeder Decoder extrahiert genau ein Feld und wird über das JSON-Feld type auf die richtige Ereignisquelle begrenzt.

Für die nachfolgenden Regeln bleibt der relevante Decodername ohne use_own_name weiterhin json. Die fachliche Eingrenzung erfolgt über dynamische Felder wie type, sentinelone_threat_confidence und sentinelone_mitigation_status.

Dieses Vorgehen erhält die generische JSON-Verarbeitung, vermeidet fragile monolithische reguläre Ausdrücke und stellt die sicherheitsrelevanten SentinelOne-Werte für Erkennung, Korrelation und Alarmierung in Wazuh bereit.

Quellen

Wazuh JSON decoder
https://documentation.wazuh.com/current/user-manual/ruleset/decoders/json-decoder.html

Wazuh Sibling Decoders
https://documentation.wazuh.com/current/user-manual/ruleset/decoders/sibling-decoders.html

Wazuh Decoder XML Syntax
https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/decoders.html

Wazuh Regular Expression Syntax
https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/regex.html

Wazuh Custom Decoders
https://documentation.wazuh.com/current/user-manual/ruleset/decoders/custom.html

Wazuh Rules Syntax
https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/rules.html

Mehr zu Wazuh …

Mehr zum Wazuh Ambassador Program …

https://wazuh.slack.com/archives/C07CCCCGHHP/p1777456224503449