SSH keys are the most powerful and least managed credentials in most enterprises. A single SSH private key can grant root access to production servers, bypass MFA, leave no meaningful audit trail, and work forever — because SSH keys have no expiry date.
When an attacker compromises one server, the first thing they look for is SSH private keys. Found keys enable lateral movement to every other server that trusts them — silently, without triggering authentication alerts, and without needing any additional credentials.
This isn’t theoretical. SSH key abuse is a documented technique in virtually every major breach involving Linux/Unix infrastructure.
Why SSH Keys Are High-Value Targets
No Expiry
Passwords expire (90-day policies). Certificates expire (90-398 days). OAuth tokens expire (hours). SSH keys? Never. A key generated in 2018 works identically in 2028 unless someone manually removes it.
No MFA
When you authenticate with an SSH key, there’s no second factor. No push notification. No TOTP code. The key IS the authentication. If someone has the key file, they have access.
No Centralized Audit
SSH key authentication is logged locally on each server (/var/log/auth.log). There’s no central “who accessed what with which key” view unless you’ve built one. An attacker using a stolen key looks identical to the legitimate user in logs.
Lateral Movement Enabler
Attacker compromises Server A (via application vulnerability)
→ Finds SSH private key on Server A (in /home/deploy/.ssh/)
→ Key is authorized on Servers B, C, D, E (shared deploy key)
→ Attacker now has access to 4 additional servers
→ Each server may have MORE keys → exponential spread
This is the SSH key lateral movement pattern. It’s in the MITRE ATT&CK framework (T1021.004 — Remote Services: SSH).
Real-World SSH Key Attacks
The Orphaned Key Attack
An engineer leaves the company. HR disables their AD account. SSO is revoked. VPN access removed. But their SSH key remains in authorized_keys on 30 servers. Six months later:
- The engineer’s laptop is stolen (or they’re disgruntled)
- The SSH key still works — it bypasses every identity control
- Access is indistinguishable from when they were employed
The Shared Service Account Key
A CI/CD pipeline uses one SSH key to deploy to 50 servers. The private key is stored as a “secret” in the CI system. But it’s also:
- On the original developer’s laptop (who generated it 3 years ago)
- In a backup of the CI system from 2023
- In a Slack message (“here’s the deploy key for the new guy”)
- In the team wiki (for “documentation”)
Any of these locations compromised = 50 servers compromised.
The Key Harvesting Attack
Attacker gains access to one server. They run:
find / -name "id_rsa" -o -name "id_ed25519" -o -name "*.pem" 2>/dev/null
find / -name "authorized_keys" 2>/dev/null
cat /home/*/.ssh/known_hosts # Shows what other servers exist
They now have: private keys (for lateral movement), authorized_keys (shows what trusts what), and known_hosts (maps the network). Complete SSH infrastructure reconnaissance in seconds.
SSH Key Protection Controls
Control 1: Passphrase-Protect All Keys
# Generate with passphrase
ssh-keygen -t ed25519 -N "strong-passphrase-here"
# Add passphrase to existing key
ssh-keygen -p -f ~/.ssh/id_ed25519
# Use ssh-agent for convenience (passphrase entered once per session)
ssh-add -t 28800 ~/.ssh/id_ed25519 # Cache for 8 hours max
A passphrase-protected key requires cracking before use. An unprotected key is immediately usable by anyone who reads the file.
Control 2: Restrict File Permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 # Private key: owner read/write only
chmod 644 ~/.ssh/id_ed25519.pub # Public key: readable
chmod 600 ~/.ssh/authorized_keys # Authorized keys: owner only
SSH refuses to use a private key with overly permissive permissions. But authorized_keys with wrong permissions may silently fail (falling back to password auth without warning).
Control 3: Inventory All Keys
# Scan all servers for authorized_keys
ansible all -m shell -a "cat /home/*/.ssh/authorized_keys 2>/dev/null | ssh-keygen -lf -"
# Count total authorized key entries
ansible all -m shell -a "wc -l /home/*/.ssh/authorized_keys 2>/dev/null"
# Find private keys on servers (shouldn't be there!)
ansible all -m shell -a "find /home /root -name 'id_*' -not -name '*.pub' 2>/dev/null"
Control 4: Remove Keys for Departed Employees
# Offboarding script: remove user's key from all servers
OLD_KEY_FINGERPRINT="SHA256:abc123..."
ansible all -m shell -a "
grep -v '$OLD_KEY_FINGERPRINT' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp
mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys
"
Control 5: Use Forced Commands (Least Privilege)
# In authorized_keys — restrict what the key can do:
command="/usr/local/bin/backup.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... backup-key
# This key can ONLY run backup.sh — nothing else
# Even if compromised, attacker can only trigger backups
Control 6: Migrate to SSH Certificates
The ultimate solution — keys with built-in expiry:
# Issue 8-hour certificate
ssh-keygen -s ca_key -I "alice@company" -n "deploy" -V "+8h" id_ed25519.pub
# Server trusts the CA, not individual keys
# TrustedUserCAKeys /etc/ssh/ca.pub
# After 8 hours: certificate expires, access ends
# No authorized_keys. No manual removal. No orphaned access.
Monitoring SSH Key Usage
What to Log
# /etc/ssh/sshd_config
LogLevel VERBOSE
# Logs: key fingerprint used for each authentication
# Example log entry:
# Accepted publickey for deploy from 10.0.1.50 port 52341 ssh2: ED25519 SHA256:abc123...
What to Alert On
| Event | Severity | Meaning |
|---|---|---|
| Key auth from unexpected IP | HIGH | Possible stolen key |
| Key auth outside business hours | MEDIUM | Investigate |
| Key auth for terminated user | CRITICAL | Orphaned key still active |
| Multiple servers accessed rapidly | HIGH | Lateral movement pattern |
| New key added to authorized_keys | MEDIUM | Unauthorized access grant |
| Private key file accessed/copied | HIGH | Key exfiltration attempt |
SIEM Integration
Feed SSH auth logs to your SIEM. Correlate with:
- HR termination events (key used after termination = incident)
- VPN/network logs (key used from unexpected location)
- Other authentication events (key used without corresponding SSO session)
The Path Forward: SSH Certificates
Static SSH keys are fundamentally flawed for enterprise use because they lack:
- Expiry (work forever)
- Centralized trust (each server manages its own authorized_keys)
- Revocation (no mechanism to invalidate a key across all servers)
- Audit (no central record of who has access to what)
SSH certificates solve all four:
| Problem | SSH Keys | SSH Certificates |
|---|---|---|
| Expiry | Never | Built-in (hours/days) |
| Trust management | Per-server authorized_keys | Central CA (one config) |
| Revocation | Manual removal from each server | Stop issuing certs (or KRL) |
| Audit | Scattered server logs | CA logs every issuance |
| Offboarding | Hunt and remove from all servers | Don’t issue new cert = access ends |
| Scaling | N users × M servers = N×M entries | N users × 1 CA = N certs |
FAQ
Q: Are SSH keys less secure than passwords? A: No — SSH keys are far MORE secure than passwords (128-bit vs ~40-bit entropy). The security problem isn’t the key itself — it’s the management (no expiry, no rotation, no inventory, no offboarding).
Q: Should I disable SSH key auth and use passwords instead? A: Absolutely not. Disable passwords, keep keys. Then improve key management (inventory, rotation, certificates). Keys with good management >> passwords with any management.
Q: How do I know if a former employee’s key is still active? A: Scan all servers for authorized_keys entries. Match key fingerprints/comments against your current employee list. Any key that doesn’t match a current employee should be investigated and likely removed.
Q: What’s the risk of SSH keys in CI/CD secrets? A: If the CI/CD platform is compromised (malicious PR, dependency attack, stolen admin credentials), the SSH key is exposed. Mitigations: use short-lived SSH certificates (issued per-pipeline-run), restrict the key to specific commands (forced command), and limit which servers trust the CI key.
Q: How often should SSH keys be rotated? A: Annually at minimum (NIST SP 800-57 guidance). Better: migrate to SSH certificates with 8-24 hour validity — rotation becomes automatic and continuous.