Separate honeypots and noise from real services before you act on scan results. Identifies 30 honeypot frameworks by behavioral signature, fingerprints 339 AI/ML platforms, and emits JSONL with confidence scores and evidence strings.
We audited the source code of every honeypot galleria detects. We found bugs and exploitable errors in all of them. Those bugs are the fingerprints. We identify honeypots by their own mistakes.
The bugs fall into two categories:
Hardcoded values — constants embedded in source that no real service produces. Single-packet, pre-auth identification, near-zero false-positive rate:
- MysqlPot (
MysqlDefs.cs): auth scramble is always 12 bytes of0x42(BBBBBBBBBBBB). Real MySQL generates a random 20-byte nonce. - Cowrie (
ssh/factory.py:44): SSH banner hardcoded toSSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2, a 2012 release. - Cowrie (
ssh/transport.py:229): KEXINIT packets null-padded. Real OpenSSH uses random padding on all packet types. Detectable passively before any auth attempt. - pghoney (
serverutils.go:67): MD5 auth salt is the same hardcoded value on every connection to every deployment. - sticky_elephant (
postgres_simulator.rb): backend PID always reported as666in ParameterStatus messages. Real PostgreSQL reports the actual OS PID. - Dionaea (
sip.py): SIP digest nonce hardcoded tofoobar123across all deployments. - Conpot (databus template): SNMP
sysLocationhardcoded to"Venus"; Guardian AST station name hardcoded to"STATOIL STATION". - OpenCanary (
mssql.py): NTLM challenge blob containsthinkst.comverbatim. - elastichoney / elasticpot (
main.go): node UUID, MAC address, and build hash identical across every deployment worldwide. - Lophiid (
backend.go:626):SendStatusdocumented as unauthenticated in source.// SendStatus is not authenticated.Any host can drain the command queue and register phantom nodes.
Behavioral bugs — implementation errors that cause the honeypot to respond differently than the real protocol requires. Detected by sending a sequence of messages and observing the deviation:
- Dionaea Memcache:
SET foo barthenGET foo. Real Memcache returnsVALUE foo. Dionaea returnsEND. Values not retained because a fresh FakeRedis instance is created per data event. - Dionaea MQTT:
CONNECTwith garbage credentials. Real brokers returnCONNACK 0x05(refused). Dionaea returnsCONNACK 0x00(accepted) unconditionally. - Dionaea SIP:
INVITEwithoutAuthorization. Real SIP servers respond407 Proxy Authentication Required. Dionaea accepts it. - nosqlpot (
redisdeploy.py:75):AUTHmissing from the command dispatch table. Every password attempt returnsunknown command 'auth'. Real Redis returns-NOAUTH Authentication required. - MongoDB-HoneyProxy: no OP_MSG handler. Message dropped silently. Real MongoDB responds.
- OpenCanary MSSQL (
mssql.py:129):bytes.find(str)raisesTypeErrorin Python 3 on the first valid TDS PRELOGIN packet. Handler crashes before logging anything. - sticky_elephant (
handler/handshake.rb):authenticate()callssend_authentication_okwithout checking the password. Every credential, including random garbage, is accepted. - Honeyd FTP:
SYSTreturnsWindows_NTon Linux hosts.
Full findings, file paths, line numbers, and exploit chains: docs/honeypot-research/.
galleria runs two stages on every host:
1. Noise floor characterization — five concurrent probes determine whether the host is running Portspoof or a similar catch-all:
- Junk-port probe (ports 7, 13, 19, 37, 79 — no real service uses these)
- Canary-port probe (64996-64998 — portspoof listens everywhere, real hosts RST)
- Decoy path probe (
/galleria-decoy-9f3a2c— catch-all returns 200, real services 404) - Cross-port sampling + timing uniformity (stddev < 15ms across ports = one process handling all)
- Malformed HTTP verb (
XYZZY-GALLERIA) — portspoof returns 200, real HTTP returns 400/405
Once the floor is confirmed, all HTTP-tier ports are marked FLOOR without probing. Binary protocols (Redis, MQTT, Modbus, SIP, MongoDB) bypass the floor. Portspoof speaks HTTP and cannot fake binary wire protocol.
2. Per-port classification — each port runs against a priority-tiered probe set:
- Binary protocols: native wire-protocol dialogs (PING/PONG, CONNACK, INVITE, OP_MSG)
- AI/ML corpus: 339-platform embedded JSON, marker-matched response probes
- Honeypot fingerprinters: static identifiers and behavioral invariants from source analysis
--fingerprintmode: multi-step behavioral probes (SET→GET memory retention, garbage-credential auth, protocol depth tests)
| Honeypot | Key fingerprint | Research |
|---|---|---|
| Cowrie | SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2 banner; KEXINIT null padding |
analysis |
| Kippo | Same SSH lineage as Cowrie; management console bound to all interfaces, no auth | analysis |
| Honeyd | FTP SYST returns Windows_NT on non-Windows hosts |
analysis |
| Dionaea | Memcache SET→GET state loss; MQTT CONNACK unconditional; SIP nonce foobar123 |
analysis |
| Glastopf | 200 on any path with uniform body; caught by floor decoy-path probe | analysis |
| OpenCanary | MySQL capability bytes 0xff 0xf7 0x08 0x02; MSSQL thinkst.com in NTLM blob |
analysis |
| Canarytokens | Kubeconfig cluster name always k8s-prod-cluster; MCP JWE default key |
analysis |
| Amun | Lotus Domino IMAP banner; POP3 220 instead of +OK; VNC missing trailing \n |
analysis |
| Conpot | SNMP sysLocation="Venus"; Guardian AST "STATOIL STATION"; Modbus FC17 stub |
analysis |
| Krawl | Login page structure markers | analysis |
| express-honeypot | PHP error page fingerprint on LFI probe | analysis |
| EoHoneypotBundle | Hidden honeypot field naming conventions | analysis |
| msurguy/Honeypot | Hidden Laravel form field pattern | analysis |
| Pasithea | HTTP 200 + <h1>404 Not Found</h1> body on port 8082 |
analysis |
| Nodepot | WordPress response markers + Node.js server header | analysis |
| Lophiid | goja JS engine response structure; SendStatus unauthenticated (documented in source) |
analysis |
| RedisHoneyPot | Static run_id; absent AUTH command; RESP type mismatch |
analysis |
| pghoney | MD5 auth salt identical on every connection | analysis |
| nosqlpot | AUTH returns unknown command 'auth'; INFO always reports 1 command |
analysis |
| sticky_elephant | pid=666 in ParameterStatus; every password accepted unconditionally |
analysis |
| SAP Cloud Active Defense | Keycloak fingerprint + clone-app HTTP markers | analysis |
| FCaptcha | CAPTCHA challenge response structure on port 3000 | analysis |
| GHH | Static PHP shell UI markers on dork paths | analysis |
| HellPot | Absent Content-Length; unbounded transfer size | analysis |
| MongoDB-HoneyProxy | No OP_MSG handler; drops message silently | analysis |
| elastichoney | Hardcoded node UUID + MAC + build hash across all deployments | analysis |
| elasticpot | Same hardcoded node UUID as elastichoney (Green Goblin config) | analysis |
| MysqlPot | Auth scramble always BBBBBBBBBBBB (0x42 * 12), single-packet pre-auth ID |
analysis |
| mysql-honeypotd | thread_id starts at 0, increments sequentially |
analysis |
| Portspoof | Makes every port look open | floor characterization stage |
Also identifies 339 AI/ML platforms including:
- LLM servers — Ollama, vLLM, LM Studio, LlamaCpp, TGI, LocalAI, LiteLLM, OpenLLM, SGLang, LMDeploy
- Vector databases — Qdrant, ChromaDB, Milvus, Weaviate, LanceDB, Pinecone, Marqo, Vald, Vespa, SemaDB
- AI gateways / proxies — LiteLLM, Portkey, Helicone, Kong AI Gateway, Javelin, Bifrost
- MLOps / experiment tracking — MLflow, Weights & Biases, Aim, Determined AI, Kubeflow, NVFlare
- Agent frameworks — LangFlow, LangGraph, AutoGen Studio, CrewAI Studio, Flowise, Dify, AnythingLLM
- Voice / TTS / ASR — Whisper, Kokoro TTS, Coqui TTS, Piper, Bark, F5-TTS, XTTS, Vosk
- Data / storage — Kafka, Redis, MinIO, Elasticsearch, ClickHouse, MongoDB, Weaviate, Cassandra
go install github.com/sshpie/galleria@latestRelease binaries: releases.
galleria <ip> --ports <port-list> [flags]
galleria 85.9.205.64 --ports 80,443,8080,11434,6333,9200
galleria 47.123.220.240 --ports 22,23,80,443,5060,1883 --fingerprint
galleria 85.9.205.64 --ports "$(cat ports.txt | tr '\n' ',')" -c 80 --out findings.jsonl
# Pipe from shodan
shodan host 85.9.205.64 -j | jq -r '.ports[]' | tr '\n' ',' | xargs galleria 85.9.205.64 --ports -p, --ports string Comma-separated port list (required)
-o, --out string Output file path; - for stdout (default: -)
-c, --concurrency int Max concurrent port probes (default: 40)
--floor-only Characterize noise floor, then exit
--fingerprint Behavioral honeypot fingerprinting on all candidates
--all-tiers Probe all tiers even when floor is confirmed
Subcommands:
mcp Start MCP server (stdio) for LLM/Claude Code tool use
JSONL to stdout (or --out). Progress to stderr. Last line is always a summary record.
{"ts":"...","ip":"47.123.220.240","port":22,"state":"HONEYPOT","honeypot_type":"cowrie","confidence":95,"evidence":"SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2 (2012 release)"}
{"ts":"...","ip":"47.123.220.240","port":6379,"state":"REAL","platform":"redis","auth_off":true,"evidence":"Redis PING->+PONG"}
{"ts":"...","ip":"47.123.220.240","port":80,"state":"FLOOR","evidence":"portspoof floor via=canary code=200 size=1842"}
{"type":"summary","ts":"...","ip":"47.123.220.240","floor_active":true,"real":1,"unknown":0,"honeypot":1,"floor":412,"honeypot_ids":[{"port":22,"honeypot_type":"cowrie","confidence":95}]}States: REAL / UNKNOWN / FLOOR / HONEYPOT
claude mcp add galleria -- galleria mcpExposes a scan tool over stdio JSON-RPC 2.0. Accepts ip, ports, fingerprint, concurrency.
Full findings in docs/honeypot-research/. Architecture and competitive comparison: docs/ARCHITECTURE.md.