Skip to content

SPRINT: EPSILON — Forensic Analysis: Breach Detection & Backdoor Sweep

Issued by: Omega v2.7 | Date: April 13, 2026 Priority: 🔴 EMERGENCY SECURITY Builder: Epsilon Time: 2-3 hrs per server (ghost-01, cx23, cx23-b) Context: Active brute force attack discovered on ghost-01 (45.148.10.147). cx23-b found with UFW inactive, PasswordAuth=yes, RootLogin=yes, no fail2ban — completely exposed. Must determine if any server was compromised.


CRITICAL CONTEXT

SSH password auth was ON with root login allowed on at least ghost-01 and cx23-b. Brute force bots hammer every Hetzner IP continuously. If any bot succeeded, they could have: - Installed crypto miners (most common) - Planted SSH backdoors (authorized_keys, SUID binaries) - Installed rootkits (kernel modules, LD_PRELOAD hooks) - Added cron jobs for persistence - Modified system binaries (replaced ssh, sshd, ls, ps, netstat with trojanized versions) - Exfiltrated credentials (.env files, API keys, database passwords) - Planted web shells in nginx-accessible directories - Added new user accounts - Installed reverse shells that phone home on a schedule

Assume compromise until proven otherwise. Check everything.


PHASE 1: IMMEDIATE TRIAGE (run first on each server)

1.1 Check for unauthorized users

# List all users with login shells
grep -E '/bin/(bash|sh|zsh|fish)' /etc/passwd
# Expected: root and maybe 1-2 service accounts. Any unknown user = COMPROMISED

# Check for users with UID 0 (root-equivalent)
awk -F: '$3 == 0 {print $1}' /etc/passwd
# Expected: only "root". Anything else = COMPROMISED

# Check for recently added users
ls -lt /home/
# Any unexpected home directories = COMPROMISED

# Check /etc/passwd and /etc/shadow modification times
stat /etc/passwd /etc/shadow
# If modified recently (after server provisioning) = SUSPICIOUS

1.2 Check SSH authorized_keys for backdoors

# Check root's authorized_keys
cat /root/.ssh/authorized_keys
# Should ONLY contain J's public key(s). Any unknown key = COMPROMISED
# Count the keys:
wc -l /root/.ssh/authorized_keys
# If more keys than expected = COMPROMISED

# Check ALL users' authorized_keys
find / -name "authorized_keys" -type f 2>/dev/null
# List content of each:
find / -name "authorized_keys" -type f -exec echo "=== {} ===" \; -exec cat {} \; 2>/dev/null

# Check for authorized_keys in unusual locations
find / -name "authorized_keys2" -type f 2>/dev/null
# authorized_keys2 is sometimes used as a stealth backdoor

1.3 Check for active connections RIGHT NOW

# Who is logged in?
w
who
last -20

# Active network connections — look for unexpected outbound
ss -tnp | grep ESTAB
# Expected: SSH from J's IP, maybe connections to Hetzner mirrors
# SUSPICIOUS: connections to unknown IPs, especially on high ports or to known C2 ranges

# DNS lookups for any suspicious IPs
for ip in $(ss -tnp | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort -u); do
  echo "$ip$(host $ip 2>/dev/null | head -1)"
done

1.4 Check for running crypto miners (most common post-breach)

# High CPU processes
top -bn1 | head -20
# Any process using >50% CPU that isn't Ollama/nginx/python = SUSPICIOUS

# Known miner process names
ps aux | grep -iE "xmrig|minergate|stratum|cryptonight|monero|kdevtmpfs|kworkerds|dbused|ksoftirqd.*miner" | grep -v grep
# Any matches = COMPROMISED

# Check for processes hiding their name
ls -la /proc/*/exe 2>/dev/null | grep -i deleted
# "(deleted)" entries = process running from a binary that's been removed (hiding tracks)

# Check /tmp and /var/tmp for suspicious binaries
ls -la /tmp/ /var/tmp/ /dev/shm/
find /tmp /var/tmp /dev/shm -type f -executable 2>/dev/null
# Executable files in temp directories = SUSPICIOUS

PHASE 2: PERSISTENCE MECHANISMS

2.1 Cron jobs (most common persistence)

# Root crontab
crontab -l 2>/dev/null
# Expected: only our jobs (checkup batch, boxing scraper, cache cleanup)
# Any curl/wget to unknown URLs, base64 decoded commands, or unfamiliar scripts = COMPROMISED

# System crontabs
cat /etc/crontab
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
cat /etc/cron.d/* 2>/dev/null

# ALL users' crontabs
for user in $(cut -f1 -d: /etc/passwd); do
  crons=$(crontab -u $user -l 2>/dev/null)
  if [ -n "$crons" ]; then
    echo "=== $user ==="
    echo "$crons"
  fi
done

# Check for cron jobs hidden via @reboot (runs on every boot)
grep -r "@reboot" /etc/cron* /var/spool/cron/ 2>/dev/null

2.2 Systemd persistence

# List ALL enabled services — look for anything unexpected
systemctl list-unit-files --state=enabled | grep -v "^$"
# Expected: ssh, nginx, ollama, ghost-router, sports-cache, checkup-relay, coturn, 
#           fail2ban, unattended-upgrades, and standard Ubuntu services
# SUSPICIOUS: anything with random/obfuscated names

# Recently created service files
find /etc/systemd/system/ /lib/systemd/system/ -name "*.service" -newer /etc/hostname -type f 2>/dev/null
# Shows services created after server provisioning

# Check for user-level systemd services (common hiding spot)
find /root/.config/systemd/ /home/*/.config/systemd/ -name "*.service" 2>/dev/null

# Check for timers (systemd equivalent of cron)
systemctl list-timers --all

2.3 SUID/SGID binaries (privilege escalation backdoors)

# Find ALL SUID binaries
find / -perm -4000 -type f 2>/dev/null | sort > /tmp/suid_current.txt
cat /tmp/suid_current.txt
# Compare against known good list:
# /usr/bin/sudo, /usr/bin/passwd, /usr/bin/chsh, /usr/bin/chfn, /usr/bin/newgrp,
# /usr/bin/gpasswd, /usr/bin/su, /usr/lib/dbus-1.0/dbus-daemon-launch-helper,
# /usr/lib/openssh/ssh-keysign, /usr/bin/mount, /usr/bin/umount, /usr/bin/fusermount3
# ANYTHING ELSE (especially in /tmp, /var, /opt, /dev) = COMPROMISED

# Check for SUID shells specifically (instant root)
find / -perm -4000 -name "*sh" -o -perm -4000 -name "*bash" 2>/dev/null | grep -v "/usr/bin/"
# Any results = CRITICAL COMPROMISE

# Find SGID binaries
find / -perm -2000 -type f 2>/dev/null | sort

2.4 Modified system binaries

# Check if critical binaries have been replaced with trojans
# Compare against package manager's known checksums
dpkg --verify 2>/dev/null | grep -E "^..5" | head -30
# "..5" in column 3 means MD5 checksum changed — binary was modified
# Expected: some config files change legitimately
# SUSPICIOUS: /usr/bin/ssh, /usr/sbin/sshd, /bin/ls, /bin/ps, /bin/netstat, /usr/bin/top, /usr/bin/find

# Check specific critical binaries
for bin in /usr/bin/ssh /usr/sbin/sshd /bin/ls /bin/ps /usr/bin/top /usr/bin/find /usr/bin/curl /usr/bin/wget; do
  if [ -f "$bin" ]; then
    echo "=== $bin ==="
    stat "$bin" | grep -E "Modify|Change"
    file "$bin"
    md5sum "$bin"
  fi
done

# Check if LD_PRELOAD is being used to intercept system calls
echo $LD_PRELOAD
cat /etc/ld.so.preload 2>/dev/null
# Should be empty. Any content = ROOTKIT

2.5 Kernel module rootkits

# List loaded kernel modules
lsmod | sort
# Compare against expected Ubuntu modules
# SUSPICIOUS: modules with random names, single-letter names, or names mimicking legitimate modules

# Check for hidden kernel modules (some rootkits hide themselves from lsmod)
ls /sys/module/ | sort > /tmp/sysmodules.txt
lsmod | awk '{print $1}' | sort > /tmp/lsmod.txt
diff /tmp/sysmodules.txt /tmp/lsmod.txt
# Significant differences = module hiding = ROOTKIT

# Check kernel log for module loading
dmesg | grep -iE "module|insmod|modprobe" | tail -20
journalctl -k | grep -iE "module" | tail -20

PHASE 3: CREDENTIAL EXPOSURE

3.1 Check if our secrets were accessed

# Check access times on sensitive files
stat /opt/ghost/airport_pipeline/.env
stat /root/.secrets/cf_api_token 2>/dev/null
stat /etc/systemd/system/checkup-relay.service
stat /etc/systemd/system/sports-cache.service
# If access times are suspicious (accessed when no one was working) = POSSIBLE EXFILTRATION

# Check bash history for suspicious commands
cat /root/.bash_history | grep -iE "curl.*pastebin\|wget.*raw\|base64\|python.*http\|nc\s\|ncat\|/dev/tcp\|scp.*@"
# These patterns indicate data exfiltration or reverse shell activity

# Check if .env files were read recently
find / -name ".env" -type f -exec stat {} \; 2>/dev/null | grep -B5 "Access:"

3.2 Inventory ALL secrets on the server

# Find all .env files
find / -name ".env" -type f 2>/dev/null

# Find files containing API keys or tokens
grep -rl "sk-ant\|sk_live\|SUPABASE\|ANTHROPIC\|TOMTOM\|api_key\|api_token\|secret" /opt/ /etc/ /root/ 2>/dev/null | grep -v ".pyc\|node_modules\|__pycache__"

# If ANY secret was potentially exposed, it needs rotation:
# - TomTom keys (19 accounts)
# - Supabase keys (anon + service_role)
# - Anthropic API key (if set)
# - Brave Search key
# - TURN server shared secret
# - Ghost app token
# - KB Worker secret

PHASE 4: NETWORK FORENSICS

4.1 Check for reverse shells or C2 beacons

# Look for processes with network connections to unusual destinations
ss -tnp | grep ESTAB | grep -v ":22\|:443\|:80\|:3478\|:11434"
# Any established connections on unexpected ports = SUSPICIOUS

# Check for listening backdoors
ss -tlnp | grep -v "127.0.0.1\|::1"
# Anything listening on 0.0.0.0 that isn't in our expected list = BACKDOOR

# Check for processes using raw sockets (rootkit communication)
ss -0np 2>/dev/null

4.2 Check DNS for data exfiltration

# Some malware exfiltrates data via DNS queries
# Check for unusual DNS traffic patterns
journalctl -u systemd-resolved --since "7 days ago" 2>/dev/null | grep -iE "NXDOMAIN|SERVFAIL" | wc -l
# Very high count = possible DNS tunneling

4.3 Check iptables for injected rules

# Check for iptables rules that might redirect traffic or hide connections
iptables -L -n -v --line-numbers
iptables -t nat -L -n -v --line-numbers
# Look for rules we didn't create — especially REDIRECT, DNAT, or MASQUERADE rules
# that could be forwarding traffic through the server

PHASE 5: FILE SYSTEM ANALYSIS

5.1 Recently modified files (last 7 days)

# Files modified in the last 7 days outside of expected directories
find / -mtime -7 -type f \
  -not -path "/proc/*" \
  -not -path "/sys/*" \
  -not -path "/run/*" \
  -not -path "/var/log/*" \
  -not -path "/var/cache/*" \
  -not -path "/tmp/*" \
  -not -path "/opt/ghost/*" \
  -not -path "/var/lib/*" \
  2>/dev/null | sort
# Review for unexpected modifications to system directories

5.2 Hidden files and directories

# Find hidden files in system directories (common hiding spots)
find /usr /var /opt /etc -name ".*" -type f 2>/dev/null | grep -v ".gitignore\|.env\|.bashrc\|.profile\|.bash_history"
# Hidden files in /usr/bin, /usr/sbin, /var/www = SUSPICIOUS

# Find hidden directories
find / -name ".*" -type d -not -path "/proc/*" -not -path "/sys/*" -not -path "/run/*" 2>/dev/null | grep -v "/\.\|\.cache\|\.config\|\.local\|\.npm\|\.ssh\|\.ollama"

5.3 World-writable files in system paths

find /usr /etc /opt -type f -perm -o+w 2>/dev/null
# World-writable files in system paths = backdoor risk

5.4 Check for web shells

# Common web shell patterns in web-accessible directories
find /var/www /opt/ghost /usr/share/nginx -type f -name "*.php" -o -name "*.asp" -o -name "*.jsp" 2>/dev/null
# We don't run PHP/ASP/JSP — any such file = WEB SHELL

grep -rl "eval\|base64_decode\|exec\|system\|passthru\|shell_exec" /var/www/ /opt/ghost/*.py 2>/dev/null
# Check results carefully — our Python code uses some of these legitimately
# But unexpected files or unexpected occurrences = SUSPICIOUS

PHASE 6: AUTH LOG ANALYSIS

# Failed login attempts (last 7 days)
journalctl -u ssh --since "7 days ago" | grep "Failed password" | wc -l
# High count is expected (bots), but look at the breakdown:

# Which usernames were tried?
journalctl -u ssh --since "7 days ago" | grep "Failed password" | awk '{print $(NF-5)}' | sort | uniq -c | sort -rn | head -20
# Expected: root, admin, ubuntu, test, etc. (standard bot wordlists)
# ALARMING: if they tried our actual username or specific service names

# SUCCESSFUL logins (this is what matters)
journalctl -u ssh --since "30 days ago" | grep "Accepted" | head -50
# Every entry here should be from J's IP address
# ANY login from an unknown IP = CONFIRMED BREACH
# Check IPs:
journalctl -u ssh --since "30 days ago" | grep "Accepted" | awk '{print $NF}' | sort -u

# Check for login at unusual times (while J was sleeping)
journalctl -u ssh --since "30 days ago" | grep "Accepted"
# Review timestamps — logins at 3-6 AM in J's timezone (Guadalajara, UTC-6) are suspicious

PHASE 7: cx23-b EMERGENCY (highest risk)

cx23-b had UFW inactive + PasswordAuth yes + RootLogin yes + no fail2ban. This is the most likely compromised server.

Run ALL phases above on cx23-b FIRST, before applying any fixes.

If compromise is confirmed: 1. Do NOT just patch it — the attacker may have persistence you haven't found 2. Document everything — screenshot/log all findings 3. Snapshot the disk (Hetzner rescue mode) for forensic preservation 4. Nuke and rebuild — fresh Ubuntu, fresh config, restore only known-good data from backups 5. Rotate ALL secrets that were on that server (TomTom keys, SSH keys, any .env content)

If no compromise found: 1. Apply all security fixes (UFW, SSH hardening, fail2ban) 2. Rotate secrets anyway (they were exposed to password brute force) 3. Monitor closely for 48 hours


COMPLETION REPORT TEMPLATE

## Epsilon Forensic Analysis — Completion Report

### Per-Server Summary

#### ghost-01
- Unauthorized users found: [YES/NO — list if yes]
- Unknown SSH keys in authorized_keys: [YES/NO — list if yes]
- Unexpected SUID binaries: [YES/NO — list if yes]
- Suspicious cron jobs: [YES/NO — list if yes]
- Suspicious systemd services: [YES/NO — list if yes]
- Modified system binaries: [YES/NO — list if yes]
- LD_PRELOAD/rootkit indicators: [YES/NO]
- Crypto miner processes: [YES/NO]
- Suspicious outbound connections: [YES/NO — list destinations]
- Successful logins from unknown IPs: [YES/NO — list IPs and times]
- Secrets potentially exposed: [list]
- VERDICT: [CLEAN / SUSPICIOUS / COMPROMISED]

#### cx23
- [Same checklist]
- VERDICT: [CLEAN / SUSPICIOUS / COMPROMISED / UNREACHABLE]

#### cx23-b
- [Same checklist]
- VERDICT: [CLEAN / SUSPICIOUS / COMPROMISED]

### Secrets That Need Rotation
[List every secret that was on a potentially compromised server]

### Fixes Applied
[List every security fix applied during or after the audit]

### Recommendations
[Post-audit security improvements]

### Monitoring Plan
[What to watch for over the next 48 hours]

WHAT NOT TO DO

  • Do NOT apply fixes on cx23-b before completing the forensic analysis — you'll destroy evidence
  • Do NOT assume "UFW blocked it" means safe — UFW can be disabled by an attacker with root
  • Do NOT skip the kernel module check — sophisticated attackers hide at the kernel level
  • Do NOT ignore successful SSH logins from unknown IPs — that's confirmed breach
  • Do NOT just rotate secrets and move on if compromise is found — nuke and rebuild
  • Do NOT rush — a thorough 2-hour forensic sweep now prevents months of hidden compromise later