Fake SSH honeypot in Go: logs attacker logins & commands per IP, web panel + loot collector
938
A fake SSH honeypot written in Go. It impersonates a high‑end GPU server on port 22, accepts ANY username and password, and records every credential and command per source IP — while never executing anything. It ships with a web analysis panel, an optional real‑malware payload collector, a password‑capture mode, Discord alerts, and a passkey‑protected dashboard.
⚠️ Defensive / research tool. Run it only on infrastructure you own and are authorized to operate. You are responsible for the data you collect (logs contain third‑party IPs and credentials) and for complying with applicable law.
data/<ip>.json and survives restarts.uname, nvidia-smi, lspci, docker ps, free, df, ps aux, /proc/*, which, version strings, etc.touch / mkdir / rm / echo > file persist across the attacker's separate connections, so wget x; ls; cat x stays consistent.grep / awk / cut / head / tail / sort / uniq / wc / sed / tr actually process the text. Supports |, &&, ;, subshells ( … ) and redirection > / >>.chmod, ./payload, wget, apt install, the echo -e \x.. "auth_ok" canary used by loaders… all fake success. Nothing ever runs.user:password list (passwds.txt) built from the captured logins.wget/curl <url>, the honeypot downloads the real file (without executing it) into /loot for analysis (extract the C2 / crypto‑wallet). Hardened: only public hosts, HTTP(S) ports only, never returns the content to the attacker.DELETE to confirm) with a persistent delete audit log.docker run -d --name ssh-honeypot --restart always \
-p 22:22 -p 8000:8000 \
-v ssh-honeypot-data:/data \
luizschmitt/ssh-honeypot:latest
your-host:22 — connect with any username/password.http://your-host:8000⚠️ Port
22must be free. On a real server, move your admin SSH to another port first (e.g. 2200), otherwise you lock yourself out.
docker run -d --name ssh-honeypot --restart always \
--read-only --cap-drop ALL --security-opt no-new-privileges \
-p 22:22 -p 8000:8000 \
-v ssh-honeypot-data:/data \
-v ssh-honeypot-loot:/loot \
-e PASSKEY="change-me" \
-e COLLECT_PAYLOADS=1 \
-e DISCORD_WEBHOOK="https://discord.com/api/webhooks/XXX/YYY" \
-e DASHBOARD_URL="http://your-host:8000" \
luizschmitt/ssh-honeypot:latest
| Variable | Default | Description |
|---|---|---|
PASSKEY | (empty) | If set, the web panel requires this passkey to open (HttpOnly session cookie, constant‑time compare). Empty/unset = panel opens with no login. Always set this if the panel is reachable by anyone. |
COLLECT_PAYLOADS | 0 | 1 / true enables the payload collector: URLs from wget / curl are actually downloaded (never executed) into /loot. Off by default. Requires outbound HTTP(S). |
DISCORD_WEBHOOK | (empty) | Discord webhook URL. If set, sends a message on every login and logout. Empty = no alerts. |
DASHBOARD_URL | http://localhost:8000 | Public URL of the panel, used to build the deep links inside the Discord alerts (e.g. http://1.2.3.4:8000). |
All four can also be passed as flags (-passkey, -collect-payloads, -discord-webhook, -dashboard-url); the env vars are just the defaults.
Append any of these after the image name to override the defaults.
| Flag | Default (image) | Description |
|---|---|---|
-addr | :22 | Bind address of the fake SSH server (e.g. :22, :2222, 0.0.0.0:22). |
-web | :8000 | Bind address of the web panel (empty string disables the panel). |
-web-only | false | Run only the panel (no SSH honeypot) — e.g. a separate analysis box reading the same volume. |
-data | /data | Directory for per‑IP records, the host key and the audit log. |
-loot | /loot | Directory for collected payloads. |
-hostname | gpu-node-01 | Hostname shown in the fake shell prompt and uname. |
-version | OpenSSH_8.9p1 | SSH version banner announced (after SSH-2.0-). |
-hostkey | <data>/host_key | Host key path (auto‑generated on first run; keep it on the volume so the fingerprint is stable). |
Example — run on a high port with a different identity:
docker run -d -p 2222:2222 -p 8000:8000 -v ssh-honeypot-data:/data \
luizschmitt/ssh-honeypot:latest -addr :2222 -web :8000 -data /data -hostname web-prod-01
| Path | Contents |
|---|---|
/data | Per‑IP records (<ip>.json), the persistent SSH host key, and the delete audit log (audit.jsonl). Mount this to keep history across container recreations. |
/loot | Collected payloads (<sha256>.bin) + index.jsonl metadata. Only written when COLLECT_PAYLOADS=1. |
| Port | Service |
|---|---|
22 | Fake SSH honeypot (the trap). |
8000 | Web analysis panel. |
http://your-host:8000 (enter the passkey if you set one).user:password dictionary. Extract just the passwords:
cut -d: -f2 passwds.txt | sort -u > passwords.txt
strings loot/<sha256>.bin | grep -iE '4[0-9AB][0-9A-Za-z]{93}|pool|stratum|xmrig'
cat loot/index.jsonl # origin URL (C2), source IP, sha256, size
169.254.169.254), validating the IP at connect time (DNS‑rebinding safe). The downloaded bytes are saved to /loot and never returned to the attacker.--read-only --cap-drop ALL --security-opt no-new-privileges (fully supported).80/443/53 only — the collector keeps working, but the honeypot can't reach anything else.8000) private — VPN / firewall / SSH tunnel. It has no auth unless you set PASSKEY, and it can wipe data.22:22.docker pull luizschmitt/ssh-honeypot:latest
docker rm -f ssh-honeypot
# re-run with the same flags/volumes
Your captured data and payloads live in the volumes, so they survive docker rm.
latest — newest build.1.x.y — pinned versions (e.g. 1.6.0). Pin a version in production.Content type
Image
Digest
sha256:a34d4ee3c…
Size
3.9 MB
Last updated
4 months ago
docker pull luizschmitt/ssh-honeypot