AWS WAF + Wazuh: Wie man „verflachte“ JSON-Header wieder brauchbar macht

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 jq sinnvoller 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-agent
  • host
  • authorization
  • 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.
Alle name-Felder landen in einer Liste, alle value-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.:

  • action
  • clientIp
  • country
  • user-agent
  • host
  • httpVersion
  • httpMethod

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 an
  • prematch → stellt sicher, dass nur WAF-Events mit webaclId betroffen sind
  • regex → zieht die gewünschten Felder in der richtigen Reihenfolge raus
  • order → weist die Match-Gruppen sprechenden Feldnamen zu

Resultat in wazuh-logtest:

  • waf_action = ALLOW
  • srcip = 13.x.x.x
  • src_country = IN
  • http_user_agent = unirest-java/1.3.11
  • http_host = api.taxbuddy.com
  • protocol = HTTP/1.1
  • http_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:

  1. JSON als Ganzes durch den Standarddecoder verarbeiten
  2. 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.clientIp
    • httpRequest.uri
    • httpRequest.host
    • labels
    • ruleGroupList
  • Zusätzlich gibt es z. B.:
    • Content-Type = application/json
    • accept-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 bei BlueWolfNinja/wazuh-resources

Beispiel-Ablauf (selbstverwaltete Umgebung):

  1. Script schreibt regelmäßig neue CloudWatch-Logs in raw.json
  2. jq -cf transform-arrays-of-objects-to-numbered-objects.jq raw.json >> processed.json
  3. Wazuh sammelt die Events aus processed.json per <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-pull einsetzen,
    • oder sich von aws-s3.py / cloudwatchlogs.py inspirieren 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:

  1. Custom Decoder in Wazuh – ideal, wenn du klar definierte Schlüssel-Felder extrahieren willst.
  2. 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

Mehr zu Wazuh …

Mehr zum Wazuh Ambassador Program …