The scenario is fictional (RFC 5737 IPs). Detection only.
A public SSH server gets thousands of failed logins a day. Alerting on all of them burns out analysts. Hidden in that noise is one line you must never miss:
Accepted password for root from 203.0.113.77
What you'll learn
- How brute force and spraying differ, and what each needs
- Picking only IPs that failed then succeeded
- Detecting spraying by distinct target accounts
- The Windows equivalents
The story at a glance
02:18 failed root logins (btmp), 02:20 root success (wtmp), 02:22 payload fetched.
Three attack shapes
| Type | Shape | Detection axis |
|---|---|---|
| Brute force | many passwords, one account, fast | failures per source IP |
| Spraying | few passwords, many accounts, slow | distinct target accounts per IP |
| Credential stuffing | leaked pairs reused | known leaks, success from unknown devices |
Failure followed by success
LOG=/var/log/auth.log; THRESHOLD=20
for ip in $(grep 'Failed password' $LOG | awk '{for(i=1;i<=NF;i++)if($i=="from")print $(i+1)}' | sort -u); do
fails=$(grep -c "Failed password.*from ${ip} " "$LOG")
succ=$(grep -c "Accepted .*from ${ip} " "$LOG")
[ "$fails" -ge "$THRESHOLD" ] && [ "$succ" -ge 1 ] && echo "[SUSPECT] ip=${ip} fails=${fails} success=${succ}"
done
On a synthetic log it printed only [SUSPECT] ip=203.0.113.77 fails=30 success=1; an IP with 50 failures and no success was ignored.
Spraying
awk '/Failed password/ {u="";ip=""; for(i=1;i<=NF;i++){if($i=="for")u=$(i+1); if($i=="invalid")u=$(i+2); if($i=="from")ip=$(i+1)} if(ip!=""&&u!="")print ip,u}' /var/log/auth.log \
| sort -u | awk '{c[$1]++} END{for(ip in c)print c[ip],ip}' | sort -rn | head
198.51.100.9, with 12 accounts failed once each, topped the list. Rule of thumb from the book: 10+ distinct accounts from one IP suggests spraying.
What to alert on
(1) brute force with a success, (2) spraying, (3) failures from internal segments.
Auth method hint
A pile of Failed password means that host allows password auth at all. Public-key-only hosts stay nearly silent.
Windows mapping
Failed password = 4625, Accepted = 4624, Invalid user = SubStatus 0xC0000064, wrong password = 0xC000006A (book; not tested here).
Next: Golden and Silver tickets, spotted by logs that aren't there.
Adapted from chapter 22 of my Japanese book on server defense.
Top comments (0)