Sign inSign up

luizschmitt/ssh-honeypot

By luizschmitt

•Updated 4 months ago

Fake SSH honeypot in Go: logs attacker logins & commands per IP, web panel + loot collector

Image
Networking
Security
Web analytics
0

938

luizschmitt/ssh-honeypot repository overview

⁠ssh-honeypot

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.


⁠What it does

  • Fake SSH server (port 22) — accepts any user/password and logs each credential tried (great for building wordlists of what attackers actually use).
  • Per‑IP capture — every session, command and credential is stored as data/<ip>.json and survives restarts.
  • Convincing fake shell — looks like a real Linux box (an 8× NVIDIA B200 GPU server):
    • Realistic uname, nvidia-smi, lspci, docker ps, free, df, ps aux, /proc/*, which, version strings, etc.
    • Mutable per‑IP filesystem — touch / mkdir / rm / echo > file persist across the attacker's separate connections, so wget x; ls; cat x stays consistent.
    • Real pipes & filters — grep / awk / cut / head / tail / sort / uniq / wc / sed / tr actually process the text. Supports |, &&, ;, subshells ( … ) and redirection > / >>.
    • Everything "succeeds" — chmod, ./payload, wget, apt install, the echo -e \x.. "auth_ok" canary used by loaders… all fake success. Nothing ever runs.
  • Web panel (port 8000) — analyze everything in the browser: overview cards, top passwords/users/commands, searchable per‑IP table, full per‑IP session detail, one‑click copy.
  • Password dictionary — downloadable, deduplicated user:password list (passwds.txt) built from the captured logins.
  • Capture mode — toggle from the panel: deny every login while still recording the credentials, so attackers keep trying combinations and your dictionary grows.
  • Payload collector (opt‑in) — when an attacker runs 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.
  • Discord alerts (optional) — a ping on login (IP / user / password) and on logout (time online, command count, deep link to the panel for that IP).
  • Delete + audit — wipe all captured data (type DELETE to confirm) with a persistent delete audit log.

⁠Quick start

docker run -d --name ssh-honeypot --restart always \
  -p 22:22 -p 8000:8000 \
  -v ssh-honeypot-data:/data \
  luizschmitt/ssh-honeypot:latest
  • SSH honeypot: your-host:22 — connect with any username/password.
  • Web panel: http://your-host:8000

⚠️ Port 22 must 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

⁠Environment variables

VariableDefaultDescription
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_PAYLOADS01 / 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_URLhttp://localhost:8000Public 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.


⁠Command‑line flags (override the default CMD)

Append any of these after the image name to override the defaults.

FlagDefault (image)Description
-addr:22Bind address of the fake SSH server (e.g. :22, :2222, 0.0.0.0:22).
-web:8000Bind address of the web panel (empty string disables the panel).
-web-onlyfalseRun only the panel (no SSH honeypot) — e.g. a separate analysis box reading the same volume.
-data/dataDirectory for per‑IP records, the host key and the audit log.
-loot/lootDirectory for collected payloads.
-hostnamegpu-node-01Hostname shown in the fake shell prompt and uname.
-versionOpenSSH_8.9p1SSH version banner announced (after SSH-2.0-).
-hostkey<data>/host_keyHost 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

⁠Volumes

PathContents
/dataPer‑IP records (<ip>.json), the persistent SSH host key, and the delete audit log (audit.jsonl). Mount this to keep history across container recreations.
/lootCollected payloads (<sha256>.bin) + index.jsonl metadata. Only written when COLLECT_PAYLOADS=1.

⁠Ports

PortService
22Fake SSH honeypot (the trap).
8000Web analysis panel.

⁠Analyzing the data

  • Open http://your-host:8000 (enter the passkey if you set one).
  • Click the Commands card → list of unique commands; click any IP row → full session detail with timestamps.
  • ⬇ passwds.txt → download the user:password dictionary. Extract just the passwords:
    cut -d: -f2 passwds.txt | sort -u > passwords.txt
    
  • 📦 loot → list / download captured payloads. Analyze them only in an isolated VM — they are real malware. Look for the wallet / pool / C2:
    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
    

⁠Security model

  • Nothing the attacker types is ever executed. The shell is fully simulated — no RCE, no reverse shells, no real file changes, no child processes.
  • Anti‑SSRF. The payload collector only fetches public IPs on common HTTP(S) ports (80/443/8080/8443). It blocks loopback, private ranges and cloud metadata (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.
  • Not a trampoline. It does not forward connections nor open attacker‑controlled ports. The only attacker‑influenced egress is the collector (a plain GET, rate‑limited, size‑capped).
⁠Hardening checklist
  • Run with --read-only --cap-drop ALL --security-opt no-new-privileges (fully supported).
  • Restrict the container's egress to 80/443/53 only — the collector keeps working, but the honeypot can't reach anything else.
  • Keep the panel (8000) private — VPN / firewall / SSH tunnel. It has no auth unless you set PASSKEY, and it can wipe data.
  • Use a dedicated host with no other services or credentials.
  • On a real server, move admin SSH off port 22 before mapping 22:22.

⁠Updating

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.

⁠Tags

  • latest — newest build.
  • 1.x.y — pinned versions (e.g. 1.6.0). Pin a version in production.

Tag summary

Content type

Image

Digest

sha256:a34d4ee3c…

Size

3.9 MB

Last updated

4 months ago

docker pull luizschmitt/ssh-honeypot