Wer AWS WAF-Logs mit Wazuh auswertet, stößt früher oder später auf ein nerviges Detail:
Arrays von Objekten werden vom JSON-Decoder „plattgemacht“ – und plötzlich ist die Zuordnung von Header-Name zu Header-Wert kaputt.
Genau das passierte Prathamesh Bakliwal, der WAF-Logs aus CloudWatch über das Wazuh-aws-s3-Modul einspeist. Die Felder waren zwar dekodiert – aber nicht so, wie er sie brauchte.
In diesem Beitrag schauen wir uns an:
- Woher das Problem kommt
- Welche Lösungsansätze Wazuh selbst bietet
- Wann Preprocessing mit
jqsinnvoller ist - Und wie man das Ganze konkret konfiguriert
Das Problem: JSON-Decoder & Arrays von Objekten
Ausgangslage:
- Logs stammen aus AWS WAF,
- werden über CloudWatch → aws-s3 wodle ins Wazuh-Backend geholt,
- und mit dem Wazuh JSON-Decoder verarbeitet.
Ein typischer Ausschnitt aus einem WAF-Log:
"httpRequest": {
"clientIp": "13.x.x.x",
"country": "IN",
"headers": [
{ "name": "user-agent", "value": "unirest-java/1.3.11" },
{ "name": "Host", "value": "api.taxbuddy.com" },
{ "name": "Content-Type", "value": "application/json" }
],
...
}
Was Wazuh daraus macht:
- Es erkennt das
headers-Array, - trennt alle
name-Felder in eine Liste, - und alle
value-Felder in eine andere Liste.
Ergebnis (vereinfacht):
httpRequest.headers.name = ["user-agent", "Host", "Content-Type", ...]httpRequest.headers.value = ["unirest-java/1.3.11", "api.taxbuddy.com", "application/json", ...]
🔻 Problem:
Die 1:1-Beziehung zwischen name und value geht verloren. Man weiß nicht mehr sicher, welcher Wert zu welchem Header gehört – und die Reihenfolge ist nicht garantiert stabil.
Das ist besonders bitter, wenn man genau bestimmte Header braucht, z. B.:
user-agenthostauthorization- spezielle API-Keys (z. B.
mongo-api-key)
Ursache: Standardverhalten des Wazuh JSON-Decoders
[Wazuh] Pablo erklärt es so:
Wenn Wazuh Arrays von Objekten verarbeitet, „flacht“ der JSON-Decoder die Struktur.
Allename-Felder landen in einer Liste, allevalue-Felder in einer anderen.
Die ursprüngliche Zuordnung geht dabei verloren.
Das ist kein Bug, sondern das momentane Design des Decoders – und es macht viele Dinge einfach, aber genau diesen Use-Case schwierig.
Lösungsweg 1: Custom Decoder in Wazuh (ohne zusätzliche Infrastruktur)
Wenn man bestimmte Felder gezielt extrahieren möchte, kann man das Problem innerhalb von Wazuh lösen – mit individuellen Decodern.
Pablo zeigt zwei Varianten:
1a) Nur die gewünschten Felder parsen (gezielt & schlank)
Hier werden gezielt wichtige Felder extrahiert, z. B.:
actionclientIpcountryuser-agenthosthttpVersionhttpMethod
Beispieldecoder:
<decoder name="aws-waf-child">
<parent>json</parent>
<prematch type="pcre2">webaclId":"arn:aws:wafv2</prematch>
<regex type="pcre2">
"action":"(\w+)".*?"clientIp":"([^"]+)","country":"([^"]+)"
.*?"name":"user-agent","value":"([^"]+)"
.*?"name":"(?i)host","value":"([^"]+)"
.*?"httpVersion":"([^"]+)","httpMethod":"(\w+)"
</regex>
<order>
waf_action, srcip, src_country,
http_user_agent, http_host,
protocol, http_method
</order>
</decoder>
Was hier passiert:
parent="json"→ knüpft an den normalen JSON-Decoder anprematch→ stellt sicher, dass nur WAF-Events mitwebaclIdbetroffen sindregex→ zieht die gewünschten Felder in der richtigen Reihenfolge rausorder→ weist die Match-Gruppen sprechenden Feldnamen zu
Resultat in wazuh-logtest:
waf_action = ALLOWsrcip = 13.x.x.xsrc_country = INhttp_user_agent = unirest-java/1.3.11http_host = api.taxbuddy.comprotocol = HTTP/1.1http_method = POST
Perfekt für Dashboards, Regeln, Alerts und SIEM-Korrelation.
1b) JSON behalten, aber einzelne Felder zusätzlich rausziehen
In manchen Fällen möchte man:
- die komplette JSON-Struktur behalten,
- aber einzelne Header trotzdem als eigene Felder haben.
Dann kann man so vorgehen:
- JSON als Ganzes durch den Standarddecoder verarbeiten
- Danach über mehrere Child-Decoder gezielt Felder extrahieren
Beispiel:
<decoder name="json_child">
<parent>json</parent>
<prematch>webaclId</prematch>
<plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>
<decoder name="json_child">
<parent>json</parent>
<regex type="pcre2">"accept-encoding","value":"(.*?)"</regex>
<order>accept-encoding</order>
</decoder>
<decoder name="json_child">
<parent>json</parent>
<regex type="pcre2">"Content-Type","value":"(.*?)"</regex>
<order>Content-Type</order>
</decoder>
<!-- u. a. für user-agent, mongo-api-key, etc. -->
Ergebnis in wazuh-logtest:
- Alle Standardfelder des JSON-Decoders bleiben erhalten, z. B.
httpRequest.clientIphttpRequest.urihttpRequest.hostlabelsruleGroupList…
- Zusätzlich gibt es z. B.:
Content-Type = application/jsonaccept-encoding = gzip- (und weitere, je nach Regex)
Diese Variante ist ideal, wenn man „das volle JSON“ behalten und gleichzeitig gezielt Schlüssel-Felder promoten möchte.
Lösungsweg 2: Preprocessing mit jq (für maximale Flexibilität)
[BlueWolfNinja] Kevin Branch bringt eine zweite Perspektive:
Das eigentliche Problem ist die Darstellung als Arrays von Objekten.
Wenn man diese vorher umformt, werden sie für Wazuh viel besser handhabbar.
Strategie:
- Statt
headers = [ {name, value}, {name, value}, ... ] - transformiert man nach
headers.1.name,headers.1.value,headers.2.name,headers.2.value, …
Beispiel-Felder nach jq-Transform:
data.httpRequest.headers.1.name = "user-agent"data.httpRequest.headers.1.value = "unirest-java/1.3.11"data.httpRequest.headers.2.name = "Host"data.httpRequest.headers.2.value = "api.taxbuddy.com"
Damit bleibt die Zuordnung sauber und Wazuh kann ganz normal darauf decodieren.
Kevin stellt dafür ein fertiges jq-Filterfile bereit:
transform-arrays-of-objects-to-numbered-objects.jq
auf GitHub beiBlueWolfNinja/wazuh-resources
Beispiel-Ablauf (selbstverwaltete Umgebung):
- Script schreibt regelmäßig neue CloudWatch-Logs in
raw.json jq -cf transform-arrays-of-objects-to-numbered-objects.jq raw.json >> processed.json- Wazuh sammelt die Events aus
processed.jsonper<localfile>
Prathamesh’ Einwand:
Logs kommen aktuell direkt von CloudWatch in Wazuh – ohne Zwischenstation.
Kevins Antwort:
- Bei self-managed Wazuh (kein Wazuh Cloud) kann man auf dem Manager:
- eigene Pull-Skripte nutzen,
aws-log-pulleinsetzen,- oder sich von
aws-s3.py/cloudwatchlogs.pyinspirieren lassen.
So entsteht eine Art „Vorverarbeitungspipeline“, ohne dass andere Server dazukommen müssen.
Wann welchen Ansatz wählen?
Wazuh-Custom-Decoder (Variante Pablo)
Geeignet, wenn:
- Du genau weißt, welche Felder du brauchst (User-Agent, Host, Action, IP, Country, …)
- Du möglichst wenig zusätzliche Infrastruktur möchtest
- Du alles direkt im Wazuh-Stack halten willst
Vorteile:
- Kein zusätzlicher Speicherort / keine extra Pipeline
- Einfache Wartung, solange sich das Logformat nicht stark ändert
- Ideal für zielgerichtete Security Use Cases
Preprocessing mit jq (Variante Kevin)
Geeignet, wenn:
- Du sehr viele unterschiedliche Felder / Header brauchst
- Du JSON-Strukturen generell „normen“ willst
- Du flexibel mit vielen AWS-Logformaten arbeitest
Vorteile:
- Saubere, stabile Feldstruktur für alle Tools, nicht nur Wazuh
- Bessere Weiterverarbeitung in anderen Systemen (z. B. SIEM, Data Lake)
- Gut kombinierbar mit Custom Decoders & Regeln
Fazit
Der Thread macht eine wichtige Eigenheit von Wazuh sichtbar:
- Der JSON-Decoder ist mächtig – aber Arrays von Objekten sind seine Schwachstelle.
- Gerade bei AWS WAF-Logs mit Header-Listen führt das zu unschönen, „verflachten“ Feldern.
Es gibt aber zwei solide Wege aus der Falle:
- Custom Decoder in Wazuh – ideal, wenn du klar definierte Schlüssel-Felder extrahieren willst.
- Vorverarbeitung mit
jq– ideal, wenn du die gesamte Struktur neu organisieren möchtest.
Je nach Umgebung (self-managed, Kubernetes, Wazuh Cloud oder Hybrid) kann man das eine oder andere Modell wählen – oder beides kombinieren.
https://wazuh.slack.com/archives/C0A933R8E/p1764058873307169