
Galah: LLM 기반 웹 허니팟.
TL;DR: Galah (/ɡəˈlɑː/ - ‘guh-laa’로 발음)는 LLM 기반 웹 허니팟으로, 다양한 애플리케이션을 모방하고 임의의 HTTP 요청에 동적으로 응답하도록 설계되었습니다. Galah는 OpenAI, GoogleAI, GCP의 Vertex AI, Anthropic, Cohere, Ollama 등 주요 LLM 제공업체를 지원합니다.
기존의 웹 허니팟이 특정 웹 애플리케이션이나 취약점을 수동으로 에뮬레이션하는 반면, Galah는 모든 HTTP 요청에 대해 HTTP 헤더와 본문 내용을 포함한 관련 응답을 동적으로 생성합니다. LLM이 생성한 응답은 구성 가능한 기간 동안 캐싱되어 동일한 요청에 대한 반복 생성을 방지하여 API 비용을 절감합니다. 캐싱은 포트별로 이루어지므로 특정 포트에서 생성된 응답이 다른 포트의 동일한 요청에 재사용되지 않습니다.
Galah는 선택적으로 들어오는 HTTP 요청을 Suricata 규칙 세트와 검사하여 메서드, URI, 헤더, 쿠키, 요청 본문 등 다양한 HTTP 버퍼와 일치시킬 수 있습니다 (현재 구현은 모든 Suricata 키워드를 지원하지 않으며 PCRE 처리는 제한적입니다). 규칙 일치를 활성화하고 구성하려면 Suricata HTTP 규칙 일치를 참조하세요.
프롬프트 구성은 이 허니팟의 핵심입니다. 구성 파일에서 프롬프트를 업데이트할 수 있지만, LLM이 지정된 JSON 형식으로 응답을 생성하도록 지시하는 부분을 유지하는 것이 중요합니다.
참고: Galah는 LLM이 HTTP 메시지를 생성하는 기능을 탐구하기 위해 재미있는 주말 프로젝트로 개발되었습니다. 이 허니팟은 네트워크 핑거프린팅 기술, LLM 제공업체 및 모델에 따른 긴 응답 시간, 비표준 응답 등 다양한 방법을 통해 식별될 수 있습니다. 지갑 고갈(Denial of Wallet) 공격을 방지하려면 LLM API에 사용량 한도를 반드시 설정하세요.
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
테스트:
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 이벤트 로그:
{
"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"
}
더 많은 예제는 여기에서 확인하세요.
galah 패키지는 독립형 라이브러리로 사용할 수 있습니다. galah.Service를 생성하고 GenerateHTTPResponse를 http.Request와 함께 호출하여 응답을 생성합니다. ConfigFile, EventLogFile 또는 CacheDBFile이 생략되면 기본 경로(config/config.yaml, event_log.json, cache.db)가 사용됩니다. RulesConfigFile을 지정하면 규칙 확인이 활성화되고, 비워두면 규칙 엔진이 완전히 비활성화됩니다. Options.Logger는 *log.Logger로 제공할 수 있으며, nil이면 Galah가 기본 텍스트 로거를 생성합니다.
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", // omit to disable rule engine
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))
이미 로드된 구성으로 서비스를 빌드할 수도 있습니다:
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", // use default if empty
CacheDBFile: "cache.db", // use default if empty
Logger: log.NewWithOptions(os.Stderr, log.Options{}),
})
규칙 확인을 완전히 비활성화하려면 서비스를 생성할 때 RulesConfigFile을 생략하거나 NewServiceFromConfig에 nil/빈 슬라이스를 전달하세요. 사용되지 않은 옵션 필드는 기본값에 의존하도록 비워둘 수 있습니다.
서비스는 라이브러리로 사용될 때 캐시 및 이벤트 로깅을 위한 헬퍼 메서드도 제공합니다:
respBytes, _ := svc.CheckCache(req, "8080") // returns nil if no cached entry
svc.LogEvent(req, llm.JSONResponse{Headers: map[string]string{"Content-Type": "text/plain"}, Body: "hi"}, "8080", "llm", nil)