
Galah: Ein LLM-gestützter Web-Honeypot.
TL;DR: Galah (/ɡəˈlɑː/ – ausgesprochen ‘guh-laa’) ist ein LLM-betriebener Web-Honeypot, der verschiedene Anwendungen nachahmen und dynamisch auf beliebige HTTP-Anfragen reagieren kann. Galah unterstützt große LLM-Anbieter, darunter OpenAI, GoogleAI, GCPs Vertex AI, Anthropic, Cohere und Ollama.
Im Gegensatz zu traditionellen Web-Honeypots, die manuell bestimmte Webanwendungen oder Schwachstellen emulieren, generiert Galah dynamisch relevante Antworten – einschließlich HTTP-Headern und Body-Inhalten – auf jede HTTP-Anfrage. Die vom LLM generierten Antworten werden für einen konfigurierbaren Zeitraum zwischengespeichert, um wiederholte Generierung für identische Anfragen zu vermeiden und API-Kosten zu senken. Der Cache ist portspezifisch, sodass für einen bestimmten Port generierte Antworten nicht für dieselbe Anfrage auf einem anderen Port wiederverwendet werden.
Galah kann optional eingehende HTTP-Anfragen gegen einen Satz von Suricata-Regeln prüfen, die auf verschiedene HTTP-Puffer wie Methode, URI, Header, Cookies und Anforderungstext abgleichen (die aktuelle Implementierung unterstützt nicht alle Suricata-Schlüsselwörter, und die PCRE-Verarbeitung ist eingeschränkt). Um die Regelabstimmung zu aktivieren und zu konfigurieren, siehe Suricata HTTP Rule Matching.
Die Prompt-Konfiguration ist bei diesem Honeypot entscheidend. Sie können den Prompt in der Konfigurationsdatei aktualisieren, aber es ist wichtig, den Abschnitt beizubehalten, der das LLM anweist, Antworten im angegebenen JSON-Format zu erzeugen.
Hinweis: Galah wurde als unterhaltsames Wochenendprojekt entwickelt, um die Fähigkeiten von LLMs bei der Erstellung von HTTP-Nachrichten zu erkunden. Der Honeypot kann durch verschiedene Methoden identifiziert werden, wie z. B. Netzwerk-Fingerprinting-Techniken, verlängerte Antwortzeiten je nach LLM-Anbieter und -Modell sowie unübliche Antworten. Um sich vor Denial-of-Wallet-Angriffen zu schützen, legen Sie unbedingt Nutzungslimits für Ihre LLM-API fest.
config.yaml.git clone [email protected]:0x4D31/galah.git
cd galah
go mod download
mkdir bin
go build -o bin/galah ./cmd/galah
./bin/galah --help
██████ █████ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██
██ ███ ███████ ██ ███████ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██ ██ ███████ ██ ██ ██ ██
llm-based web honeypot | version 1.1.1
author: Adel "0x4D31" Ka
Usage: galah --provider PROVIDER --model MODEL [--server-url SERVER-URL] [--temperature TEMPERATURE] [--api-key API-KEY] [--cloud-location CLOUD-LOCATION] [--cloud-project CLOUD-PROJECT] [--interface INTERFACE] [--config-file CONFIG-FILE] [--rules-config-file RULES-CONFIG-FILE] [--event-log-file EVENT-LOG-FILE] [--cache-db-file CACHE-DB-FILE] [--cache-duration CACHE-DURATION] [--log-level LOG-LEVEL] [--suricata-enabled] [--suricata-rules-dir SURICATA-RULES-DIR]
Options:
--provider PROVIDER, -p PROVIDER
LLM provider (openai, googleai, gcp-vertex, anthropic, cohere, ollama) [env: LLM_PROVIDER]
--model MODEL, -m MODEL
LLM model (e.g. gpt-3.5-turbo-1106, gemini-1.5-pro-preview-0409) [env: LLM_MODEL]
--server-url SERVER-URL, -u SERVER-URL
LLM Server URL (required for Ollama) [env: LLM_SERVER_URL]
--temperature TEMPERATURE, -t TEMPERATURE
LLM sampling temperature (0-2). Higher values make the output more random [default: 1, env: LLM_TEMPERATURE]
--api-key API-KEY, -k API-KEY
LLM API Key [env: LLM_API_KEY]
--cloud-location CLOUD-LOCATION
LLM cloud location region (required for GCP's Vertex AI) [env: LLM_CLOUD_LOCATION]
--cloud-project CLOUD-PROJECT
LLM cloud project ID (required for GCP's Vertex AI) [env: LLM_CLOUD_PROJECT]
--interface INTERFACE, -i INTERFACE
interface to serve on
--config-file CONFIG-FILE, -c CONFIG-FILE
Path to config file [default: config/config.yaml]
--rules-config-file RULES-CONFIG-FILE, -r RULES-CONFIG-FILE
Path to rules config file (rule engine disabled if omitted)
--event-log-file EVENT-LOG-FILE, -o EVENT-LOG-FILE
Path to event log file [default: event_log.json]
--cache-db-file CACHE-DB-FILE, -f CACHE-DB-FILE
Path to database file for response caching [default: cache.db]
--cache-duration CACHE-DURATION, -d CACHE-DURATION
Cache duration for generated responses (in hours). Use 0 to disable caching, and -1 for unlimited caching (no expiration). [default: 24]
--log-level LOG-LEVEL, -l LOG-LEVEL
Log level (debug, info, error, fatal) [default: info]
--suricata-enabled Enable Suricata HTTP rule checking (default: false)
--suricata-rules-dir SURICATA-RULES-DIR
Directory containing Suricata .rules files to check HTTP requests against
--help, -h display this help and exit
% git clone [email protected]:0x4D31/galah.git
% cd galah
% mkdir logs
% export LLM_API_KEY=your-api-key
% docker build -t galah-image .
% docker run -d --name galah-container -p 8080:8080 -v $(pwd)/logs:/galah/logs -e LLM_API_KEY galah-image -o logs/galah.json -p openai -m gpt-3.5-turbo-1106
./galah -p openai -m gpt-4.1-mini --suricata-enabled --suricata-rules-dir rules
Test:
curl --http1.1 --path-as-is -X POST \
-H 'SOAPAction: "http://purenetworks.com/HNAP1/GetGuestNetworkSettings"' \
-H 'Content-Type: text/xml' \
--data '<GetGuestNetworkSettings xmlns="http://purenetworks.com/HNAP1/">' \
http://127.0.0.1:8888/HNAP1/ -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:8888...
* Connected to 127.0.0.1 (127.0.0.1) port 8888
> POST /HNAP1/ HTTP/1.1
> Host: 127.0.0.1:8888
> User-Agent: curl/8.7.1
> Accept: */*
> SOAPAction: "http://purenetworks.com/HNAP1/GetGuestNetworkSettings"
> Content-Type: text/xml
> Content-Length: 64
>
* upload completely sent off: 64 bytes
< HTTP/1.1 200 OK
< Server: TP-LINK HTTP Server/1.0
< Date: Mon, 21 Apr 2025 01:28:43 GMT
< Content-Length: 545
< Content-Type: text/xml; charset=utf-8
<
<?xml version="1.0" encoding="utf-8"?>
<GetGuestNetworkSettingsResponse xmlns="http://purenetworks.com/HNAP1/">
<GetGuestNetworkSettingsResult>OK</GetGuestNetworkSettingsResult>
<GuestNetworkEnabled>true</GuestNetworkEnabled>
<GuestNetworkSSID>TPLink_Guest</GuestNetworkSSID>
<GuestNetworkSecurity>WPA2-PSK</GuestNetworkSecurity>
<GuestNetworkPassword>guest1234</GuestNetworkPassword>
<GuestNetworkIsolation>true</GuestNetworkIsolation>
<GuestNetworkSSIDBroadcast>true</GuestNetworkSSIDBroadcast>
JSON-Ereignisprotokoll:
{
"eventTime": "2025-04-21T02:28:43.583386+01:00",
"httpRequest": {
"body": "<GetGuestNetworkSettings xmlns=\"http://purenetworks.com/HNAP1/\">",
"bodySha256": "836c42168ebbad0b7192daa70ad8e4ea8d5930097162f513045f5ecb6ae9d5bd",
"headers": {
"Accept": "*/*",
"Content-Length": "64",
"Content-Type": "text/xml",
"Soapaction": "\"http://purenetworks.com/HNAP1/GetGuestNetworkSettings\"",
"User-Agent": "curl/8.7.1"
},
"headersSorted": "Accept,Content-Length,Content-Type,Soapaction,User-Agent",
"headersSortedSha256": "3a44fecf9284eca3947c45ffeb2301ce6d9b3d0a3cc5a7491ccea2b6ed61edaa",
"method": "POST",
"protocolVersion": "HTTP/1.1",
"request": "/HNAP1/",
"sessionID": "1745198923587092000_qHLNEPBNPrH6qw==",
"userAgent": "curl/8.7.1"
},
"httpResponse": {
"headers": {
"Content-Length": "454",
"Content-Type": "text/xml; charset=utf-8",
"Server": "TP-LINK HTTP Server/1.0"
},
"body": "<?xml version=\"1.0\" encoding=\"utf-8\"?> <GetGuestNetworkSettingsResponse xmlns=\"http://purenetworks.com/HNAP1/\"> <GetGuestNetworkSettingsResult>OK</GetGuestNetworkSettingsResult> <GuestNetworkEnabled>true</GuestNetworkEnabled> <GuestNetworkSSID>TPLink_Guest</GuestNetworkSSID> <GuestNetworkSecurity>WPA2-PSK</GuestNetworkSecurity> <GuestNetworkPassword>guest1234</GuestNetworkPassword> <GuestNetworkIsolation>true</GuestNetworkIsolation> <GuestNetworkSSIDBroadcast>true</GuestNetworkSSIDBroadcast> </GetGuestNetworkSettingsResponse>"
},
"level": "info",
"msg": "successfulResponse",
"port": "8888",
"responseMetadata": {
"generationSource": "llm",
"info": {
"model": "gpt-4.1-mini",
"provider": "openai",
"temperature": 1
}
},
"sensorName": "mbp",
"srcHost": "localhost",
"srcIP": "127.0.0.1",
"srcPort": "62418",
"suricataMatches": [
{
"msg": "ET WEB_SPECIFIC_APPS D-Link DIR-823G Multiple HNAP SOAPAction Endpoints Authentication Bypass",
"sid": "2061623"
}
],
"tags": null,
"time": "2025-04-21T02:28:43.587152+01:00"
}
Weitere Beispiele finden Sie hier.
Das Paket galah kann als eigenständige Bibliothek verwendet werden. Erstellen Sie einen galah.Service und rufen Sie GenerateHTTPResponse mit einer http.Request auf, um eine Antwort zu erzeugen. Wenn ConfigFile, EventLogFile oder CacheDBFile nicht angegeben werden, werden deren Standardpfade (config/config.yaml, event_log.json und cache.db) verwendet. Geben Sie RulesConfigFile an, um die Regelprüfung zu aktivieren; wenn Sie es leer lassen, wird die Regelengine vollständig deaktiviert. Options.Logger kann mit einem *log.Logger versehen werden; ist es nil, erstellt Galah einen Standard-Textlogger.
svc, err := galah.NewService(context.Background(), galah.Options{
LLMProvider: "openai",
LLMModel: "gpt-4.1-mini",
LLMAPIKey: "YOUR_KEY",
ConfigFile: "config/config.yaml",
RulesConfigFile: "config/rules.yaml", // weglassen, um Regelengine zu deaktivieren
EventLogFile: "event_log.json",
CacheDBFile: "cache.db",
Logger: log.NewWithOptions(os.Stderr, log.Options{}),
})
if err != nil {
log.Fatal(err)
}
req, _ := http.NewRequest("GET", "https://example.com", nil)
respBytes, err := svc.GenerateHTTPResponse(req, "8080")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(respBytes))
Sie können auch einen Dienst aus einer bereits geladenen Konfiguration erstellen:
cfg, _ := config.LoadConfig("config.yaml")
rulesCfg, _ := config.LoadRules("rules.yaml")
svc, err := galah.NewServiceFromConfig(context.Background(), cfg, rulesCfg.Rules, galah.Options{
LLMProvider: "openai",
LLMModel: "gpt-4.1-mini",
LLMAPIKey: "YOUR_KEY",
EventLogFile: "event_log.json", // Standardwert verwenden, wenn leer
CacheDBFile: "cache.db", // Standardwert verwenden, wenn leer
Logger: log.NewWithOptions(os.Stderr, log.Options{}),
})
Um die Regelprüfung vollständig zu deaktivieren, lassen Sie RulesConfigFile beim Erstellen eines Dienstes weg oder übergeben Sie nil/ein leeres Slice an NewServiceFromConfig. Nicht verwendete Optionsfelder können leer gelassen werden, um auf die Standardwerte zurückzugreifen.
Der Dienst stellt auch Hilfsmethoden für Cache und Ereignisprotokollierung bereit, wenn er als Bibliothek verwendet wird:
respBytes, _ := svc.CheckCache(req, "8080") // gibt nil zurück, wenn kein Cache-Eintrag
svc.LogEvent(req, llm.JSONResponse{Headers: map[string]string{"Content-Type": "text/plain"}, Body: "hi"}, "8080", "llm", nil)