The scenario is fictional (RFC 5737 and 10.0.0.0/8 addresses).
After the first server falls, attackers move sideways. Lateral movement detection sounds hard. But one rule makes it a confession:
Internal servers accept SSH only from the bastion.
What you'll learn
- Why defining "impossible" traffic simplifies detection
- A one-liner for auth.log, plus network-side evidence
- How auth method hints at the root cause
- Handling false positives
The story at a glance
Bastion 10.0.0.9, compromised web03 (10.0.0.50), database db01 (10.0.0.60, bastion-only SSH).
| Time | What happened |
|---|---|
| 02:31 |
10.0.0.50 -> 10.0.0.60:22 traffic |
| 02:35 | SSH success on db01, not via the bastion |
| 02:41 | Logs and history wiped on web03
|
Detector
awk '/Accepted/{for(i=1;i<=NF;i++)if($i=="from")ip=$(i+1); if(ip!="10.0.0.9")print $1,$2,$3,$9,$11}' /var/log/auth.log
Tested on a synthetic log: it outputs only the two non-bastion successes (admin from 10.0.0.50, www-data from 203.0.113.77), ignores bastion logins and failures.
Network-side evidence (book examples, untested here)
Zeek conn.log: id.orig_h=10.0.0.50 id.resp_h=10.0.0.60 id.resp_p=22. Servers can erase their own logs; the network can't be erased by the host.
Auth method clues
Accepted password for www-data from 203.0.113.77 is itself abnormal: a service account with interactive password SSH from outside.
False positives
Ansible, backup, monitoring: allow-list sources, accounts and times. Be able to say why each entry is there.
Best defense
Firewall: SSH to internal servers only from the bastion. Use ssh -J admin@10.0.0.9 admin@10.0.0.60. Detection is the safety net.
Next: the cleanup, and how deleted logs make noise.
Adapted from chapters 9, 18 and 27 of my Japanese book on server defense.













