End-to-end SSH brute-force detection and response pipeline on Linux. A real-time journald-following detector emits structured alerts, a separate responder posts them to a webhook, and Fail2Ban + UFW provide defense-in-depth. Everything is driven from one config file and brought up with one make command.
The lab runs on a Proxmox VE hypervisor with two VMs on an isolated internal subnet — Ubuntu Server as the target, Kali Linux as the attacker.
| Component | Detail |
|---|---|
| Hypervisor | Proxmox VE 9.x (see proxmox-homelab) |
| Target | Ubuntu Server 24.04 LTS — 10.10.10.20 |
| Attacker | Kali Linux — 10.10.10.30 |
| Internal network | 10.10.10.0/24 on Proxmox bridge vmbr1 (no internet exposure) |
| External network | Proxmox bridge vmbr0 (DHCP, internet egress only) |
| Attack surface | SSH (port 22) |
| Defense layer | UFW (network) + Fail2Ban (host, journald backend) |
| Detection | Python daemon following journalctl -u ssh -f -o json |
| Response | Python daemon tailing JSONL alerts → webhook POST |
| Latency | Attack to webhook delivery: ~2 seconds |
On the Ubuntu target:
git clone https://github.com/xbadev/ssh-detection-response.git
cd ssh-detection-response
sudo make install
sudo make config # set ALERT_WEBHOOK_URL and verify IPs
sudo make enableOn the Kali attacker (after copying lab.env to /etc/ssh-detection-response/lab.env):
cd ssh-detection-response
./02-attack-simulation/run-attack.shWithin ~2 seconds, the alert lands on the webhook. To watch the pipeline live on the target:
sudo journalctl -u ssh-detector -u ssh-responder -f| # | Phase | What it does |
|---|---|---|
| 01 | Environment Setup | Proxmox dual-NIC layout, netplan config matched by MAC, SSH daemon hardening |
| 02 | Attack Simulation | run-attack.sh Hydra runner that reads target IP and user from lab.env |
| 03 | Defense Hardening | UFW restricting SSH to internal subnet, Fail2Ban with systemd-journal backend |
| 04 | Detection & Monitoring | Real-time detector daemon following the journal, sliding-window threshold, JSONL output |
| 05 | Automated Response | Real-time responder daemon tailing JSONL, webhook POST, per-IP cooldown |
┌──────────────┐ SSH brute-force ┌────────────────────────────────────────┐
│ Kali │ ───────────────────► │ Ubuntu target │
│ 10.10.10.30 │ │ 10.10.10.20 │
└──────────────┘ │ │
│ ┌─────────────┐ │
│ │ sshd │ │
│ └──────┬──────┘ │
│ │ writes failures │
│ ▼ │
│ ┌─────────────┐ │
│ │ journald │ ◄── Fail2Ban also reads│
│ └──────┬──────┘ and bans │
│ │ -f │
│ ▼ │
│ ┌─────────────┐ │
│ │ ssh-detector│ ──► alerts.jsonl │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ ssh-responder │ ──► webhook ────► Discord
│ └─────────────┘ │
└─────────────────────────────────────────┘
Detection and response are intentionally split: detector owns the event stream, responder owns the secret (webhook URL) and the network egress. Either side can be developed, tested, or replaced independently — same pattern as a SOAR detection-vs-action split.
All settings live in /etc/ssh-detection-response/lab.env (mode 0640, sourced by both systemd units). See lab.env.example for every supported variable.
sudo make config # opens lab.env in $EDITORChange subnet, IPs, threshold, or webhook URL there and restart with sudo systemctl restart ssh-detector ssh-responder.
| Command | Purpose |
|---|---|
sudo make install |
First-time install: service user, directories, units |
sudo make config |
Open /etc/ssh-detection-response/lab.env in $EDITOR |
sudo make enable |
Enable and start both services |
sudo make disable |
Stop and disable both services |
make status |
Service status + last 5 alerts |
sudo make demo |
Reset state and print attacker-side instructions |
sudo make reset |
Wipe runtime state and alert history |
sudo make uninstall |
Remove services and runtime files (keeps logs) |
- The detector is pinned to read only from sshd via
journalctl -u ssh.service -u sshd.service. There is no log file parsing. - All file output goes under
/var/log/ssh-detection-response/and/var/lib/ssh-detection-response/— standard FHS locations, owned by an unprivileged service accountssh-dr, never the developer's user. - The systemd units use modern hardening directives (
ProtectSystem=strict,NoNewPrivileges,ReadWritePaths). Runsystemd-analyze security ssh-detectorto verify. - Fail2Ban uses
backend=systemdso it reads the same data source as the detector — no/var/log/auth.logdependency. - The detector and responder are intentionally idempotent on restart and handle log rotation / file truncation correctly.