Splunk: Effiziente Normalisierung für das Common Information Model (CIM)
Martin Müller /
12.03.25 /
IT Security
Splunk verarbeitet Daten von unzähligen Systemen und Herstellern, in vielen Fällen ist die Quintessenz eines Events dennoch gleich. Im einfachsten Beispiel will das SOC von einem Anmelde-Event wissen, wer sich wann und woher an welchem System angemeldet hat und ob dies erfolgreich war oder nicht – egal, ob es sich um eine Datenbank, eine Firewall oder eine selbst entwickelte Webanwendung handelt. Um die Arbeit mit gleichbedeutenden Events unterschiedlicher Systeme zu vereinfachen, hat Splunk das Common Information Model (CIM) entwickelt. Das CIM trifft für rund zwei Dutzend Datentypen Vereinbarungen, die am Beispiel des Datenmodells Authentication folgende Aspekte vereinheitlichen sollen:
- Feldnamen
Jeder Hersteller nennt seine Felder anders. In Anmelde-Events könnte der Benutzername user, username, user_name, userName, usw. heißen. Das CIM vereinheitlicht dieses Feldnamen-Chaos: Ein CIM-kompatibles Anmelde-Event hat immer ein Feld user für den Benutzernamen. - Feldwerte
Nach Schritt 1 existiert immer ein Feld mit dem Namen action, um anzugeben, ob die Anmeldung erfolgreich war oder nicht. Für jedes System kann unterschiedlich sein, welcher Wert für “erfolgreich” steht. Zur Vereinheitlichung schreibt das CIM vor, dass Events für erfolgreiche Anmeldungen action=”success” und für fehlgeschlagene Anmeldungen action=”failure” enthalten. - Tags
Jedes Datenmodell erwartet Tags, anhand derer passende Events zu identifizieren sind. Im Beispiel Authentication muss jedes Anmelde-Event mit tag=”authentication” gekennzeichnet sein, um CIM-kompatibel zu sein.
Dank dieser CIM-Normalisierung ist es nun möglich, kreuz und quer über unzählige verschiedene Systeme einheitliche Suchen auszuführen:
tag=authentication user=admin action=success
Dies findet alle Anmelde-Events, bei denen erfolgreich der Nutzer admin benutzt wurde – unabhängig davon, wie das jeweilige System seine Logs schreibt. So weit die Theorie. Für jeden dieser drei Schritte gibt es viele Wege, um CIM-Kompatibilität zu erreichen. Einige oft gesehene Muster sind jedoch ineffizient, schauen wir uns daher jeweils Beispiele an und beleuchten die Unterschiede.
Fallstricke vermeiden
Feldnamen (statisch)
Die wahrscheinlich am meisten verbreitete Methode zur Normalisierung von Feldnamen sind Feldaliasse. Mit dieser Konfiguration wird beispielsweise ein produktspezifisches Feld user_name extrahiert und danach auch unter dem Namen user zur Verfügung gestellt.
[my_authentication_log]
EXTRACT-user_name = Login detected for (?<user_name>\S+)\.
FIELDALIAS-cim = user_name AS user
Wird jetzt nach user=admin gesucht, passiert im Hintergrund eine Übersetzung in (user=admin OR (sourcetype=my_authentication_log AND user_name=admin)). Für sich genommen ist das gut und schnell, in realistischen komplexen Umgebungen summiert sich hier allerdings eine große Menge an Alternativen. Zahlenbeispiel: Eine Suche nach 50 verschiedenen Benutzern wird übersetzt, die Splunk-Umgebung kennt 40 verschiedene Systeme mit jeweils 5 Sourcetypes. Hat jeder Sourcetype einen Alias für den Benutzernamen, entstehen so im Hintergrund 10.000 Alternativen.
Daher sollte, soweit möglich, direkt der CIM-kompatible Feldname zusammen mit dem produktspezifischen extrahiert werden:
[my_authentication_log]
EXTRACT-user_name = Login detected for (?<user>(?<user_name>\S+))\.
So trägt dieser Sourcetype nicht weiter zu der großen Menge an im Hintergrund übersetzten Alternativen bei.
Feldnamen (dynamisch)
In einigen Fällen soll dynamisch entschieden werden, welches Feld die Quelle für einen Alias sein soll – in Windows Security Eventlogs wird beispielsweise das Feld src je nach EventCode aus den Feldern Caller_Domain, ClientAddress, IpAddress, WorkstationName, SourceAddress und weiteren ausgewählt. Dies ist allerdings mit einem Feldalias in Splunk nicht möglich. Stattdessen wird auf ein berechnetes Feld zurückgegriffen, das hierfür Fallunterscheidungen anbietet:
[my_authentication_log]
EVAL-user = case(fall=”a”, user_name, fall=”b”, Benutzername, true(), user)
Für die Suche ist dieser EVAL-Ausdruck leider undurchsichtig, sodass eine Suche nach user=admin zunächst alle Events lädt, die benötigten Felder berechnet und erst dann filtert. Das gewohnt schnelle Vor-Filtern anhand der gespeicherten Index-Strukturen findet nicht statt. Solch eine Situation ist neben der langsamen Geschwindigkeit auch an einem schlechten Verhältnis zwischen scanCount und eventCount zu erkennen:
Mehr dazu gibt’s im Blogpost Suchen verstehen mit dem Job Inspector.
Für if()-Ausdrücke hat Splunk eine gewisse Durchsichtigkeit implementiert. Trotz oft schlechterer Lesbarkeit wäre folgende Variante in der Ausführung deutlich schneller:
[my_authentication_log]
EVAL-user = if(fall=”a”, user_name, if(fall=”b”, Benutzername, user))
Wird nun nach user=admin gesucht, ist die Software schlau genug, nur Events zu laden, die das Wort “admin” enthalten. Nur für diese Events wird dann diese Berechnung durchgeführt und geprüft, ob “admin” wirklich im Feld user steht. In den meisten Fällen ist die Suche so um Größenordnungen schneller.
Feldwerte
Wenn unsere Anmelde-Events Erfolg=ja und Erfolg=nein enthalten, genügen Feldaliasse nicht – wir müssen auch die Werte ändern, um action=success und action=failure zu erhalten. Ähnlich zur Lösung im vorherigen Abschnitt würde folgender if()-Ausdruck nahe liegen, um dies einfach zu lösen:
[my_authentication_log]
EVAL-action = if(Erfolg=”ja”, “success”, if(Erfolg=”nein”, “failure”, null()))
Für das direkte Durchreichen von Feldern wie im Abschnitt "Feldwerte (dynamisch)" oben wäre dies effizient ausführbar. Sobald wir allerdings selbst Werte errechnen, greift wieder der langsame "alles laden, Feldwert berechnen, dann filtern"-Modus.
Besser wäre hier der Einsatz eines automatischen Lookups zum Übersetzen anhand folgender Tabelle my_authentication_log_action:
Erfolg |
action |
ja |
success |
nein |
failure |
Diese Lookup-Tabelle kann nun verwendet werden, um für unseren Sourcetype durch Nachschlagen des Feldes Erfolg die Werte fürs Feld action zu übersetzen:
[my_authentication_log]
LOOKUP-action = my_authentication_log_action Erfolg OUTPUT action
Suchen wir nun nach action=failure, wird dies im Hintergrund durch Nachschlagen so übersetzt:
action=failure OR (sourcetype=my_authentication_log AND Erfolg=nein)
Aus unserem Sourcetype werden dann nur die Events geladen, die Erfolg=nein enthalten, anstatt alles von der Festplatte zu holen.
Tags
In aller Regel werden Eventtypes benutzt, um einem Suchfilter den Tag authentication zu geben. Dabei sollten alle üblichen Regeln zur Suchoptimierung beachtet werden, wie etwa das Angeben von Standard-Feldern wie source oder sourcetype, die Verwendung positiver Filter anstelle von negierten oder das Vermeiden von Wildcards am Anfang von gesuchten Werten.
In diesem Blog beschäftigen wir uns allerdings mit spezifischen Fallstricken bei der CIM-Normalisierung, daher hier ein Beispiel aus einer älteren Version des Add-ons für Windows:
[windows_security_authentication]
search = (source=WinEventLog:Security OR source=XmlWinEventLog:Security) (signature_id=4624 OR signature_id=4625 OR signature_id=4672)
Auf den ersten Blick wurden hier die üblichen Regeln beachtet:
- über source werden die Windows-Security-Eventlogs direkt adressiert
- keine Wildcards am Anfang und keine Negierungen
- die drei signature_id-Werte sind zielgerichtet: Es gibt sehr wenige Events, die den String 4624 im Text der Events enthalten, aber keine Anmelde-Events mit signature_id=4624 sind
Im Kontext der CIM-Normalisierung lauert hier aber ein Problem: Wenn in der Splunk-Umgebung viele Add-ons installiert sind, gibt es viele Varianten für signature_id - Feldaliasse, berechnete Felder oder automatische Lookups. Entsprechend ist die Übersetzung von signature_id=4624 relativ aufwändig und liefert einen sehr langen Filter-String im Hintergrund. Da der Eventtype sich nur um Windows-Security-Logs kümmern muss, profitieren wir hier nicht von den Vorteilen der CIM-Normalisierung, dieser Aufwand ist also verschwendet.
Besser ist es, in Eventtypes direkt die herstellerspezifischen Felder zu verwenden. Aktuelle Versionen des Add-ons für Windows machen dies so:
[windows_security_authentication]
search = (source=WinEventLog:Security OR source=XmlWinEventLog:Security) (EventCode=4624 OR EventCode=4625 OR EventCode=4672)
EventCode wird direkt aus den Windows-Eventlogs extrahiert, keine anderen Add-ons stellen dieses Feld bereit. Daher muss bei der Übersetzung dieses Eventtypes kein unnötiger Aufwand betrieben werden und sowohl die Startzeit als auch die Ausführung der Suchen werden deutlich schneller.
Fazit
Für sich betrachtet ist jeder einzelne Schritt oben ein sehr kleiner Effekt. Allerdings multiplizieren sich die Schritte gegenseitig zu einem potenziell großen Problem. Daher sollte man bei der Entwicklung eines Add-ons versuchen, auch nur kleine Ineffizienzen in der Felddefinition zu vermeiden – in der realen Welt könnten sie Auswirkungen auf Dutzende andere Add-ons haben. Andersherum können kleine Ineffizienzen in der Eventtype-Definition für sich betrachtet irrelevant erscheinen, unter dem Einfluss Dutzender anderer Add-ons aber große Nachteile mit sich bringen.