How to Investigate a Compromised Linux Server: IR Playbook

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
A compromised Linux server investigation differs from Windows IR in several important ways: there is no central event log registry, persistence spans multiple mechanisms (cron, systemd, SSH keys, bash profiles, PAM modules), and attackers frequently modify timestamps and clean logs: making order of operations critical.
The golden rule: collect volatile evidence first, examine disk artifacts second. Rebooting the server before collecting network connections and process state loses evidence that would identify the C2 IP, the malware running in memory, and the attacker's active session.
Phase 1: Volatile Evidence Collection
Run these commands IMMEDIATELY: before any other action:
# 1. Capture system information snapshot:
date && hostname && uname -a > /tmp/snapshot.txt
# 2. Who is currently logged in:
w # Current sessions with IP and activity
last -100 # Last 100 logins (reads /var/log/wtmp)
lastb -100 # Failed logins (/var/log/btmp)
who -a # All logged-in users with TTY
# 3. Running processes:
ps auxf # All processes with full args, in tree format
# Look for: unusual process names, high CPU/memory, unexpected users
# pay attention to: www-data, apache, nginx spawning shells
# 4. Process network connections:
ss -tnpu # All TCP/UDP connections with process info
# or:
netstat -tnpu
# Look for: ESTABLISHED connections to external IPs from unexpected processes
# Look for: LISTEN on unusual ports (backdoors typically listen on high ports)
# 5. Network statistics:
ss -s # Socket summary
ip route show # Routing table (detect unauthorized routes added)
arp -a # ARP table (detect lateral movement neighbors)
# 6. Active network connections WITH process names and PIDs:
ss -tnpO
# Save to file:
ss -tnpO > /tmp/network_connections_$(date +%s).txt
# 7. List all open files and network sockets per process:
lsof -i -n -P # All open network files (no DNS resolution)
lsof -u root # All files opened by root
# 8. Capture process list with hashes (before any cleanup):
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
exe=$(readlink -f /proc/$pid/exe 2>/dev/null)
cmd=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')
if [ -n "$exe" ]; then
hash=$(sha256sum "$exe" 2>/dev/null | cut -d' ' -f1)
echo "PID:$pid EXE:$exe HASH:$hash CMD:$cmd"
fi
done > /tmp/process_hashes.txt
Phase 2: Persistence Mechanism Examination
Examine all persistence locations: attackers commonly use multiple:
# SSH authorized keys (most common persistence):
for user in $(cut -d: -f1 /etc/passwd); do
home=$(getent passwd $user | cut -d: -f6)
keyfile="$home/.ssh/authorized_keys"
if [ -f "$keyfile" ]; then
echo "=== $user: $keyfile ==="
ls -la "$keyfile"
cat "$keyfile"
fi
done
# Also check /root/.ssh/authorized_keys directly:
cat /root/.ssh/authorized_keys 2>/dev/null
# Cron jobs (all users and system):
crontab -l -u root 2>/dev/null
for user in $(cut -d: -f1 /etc/passwd); do
crontab -l -u $user 2>/dev/null && echo "--- for $user ---"
done
cat /etc/crontab
ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/
cat /etc/cron.d/* 2>/dev/null
# Systemd units (persistent service-based backdoors):
find /etc/systemd /lib/systemd /usr/lib/systemd -name "*.service" \
-newer /usr/bin/systemd -ls 2>/dev/null
# Units newer than systemd binary = recently added (suspicious)
systemctl list-units --type=service --state=running
# Bash profile and rc files (executed on login/shell start):
for f in /etc/profile /etc/profile.d/*.sh /root/.bashrc /root/.bash_profile \
/root/.profile; do
echo "=== $f ==="
cat "$f" 2>/dev/null
done
# PAM modules (rootkit indicator: modified PAM auth):
cat /etc/pam.d/sshd /etc/pam.d/common-auth
ls -la /lib/x86_64-linux-gnu/security/ # Check for unexpected PAM modules
Find recently modified files (most critical):
# Files modified in the last 7 days (adjust -mtime as needed):
find / -mtime -7 -type f -not -path '/proc/*' -not -path '/sys/*' \
-not -path '/dev/*' -ls 2>/dev/null | sort -k8,9
# Specifically: recently modified SUID binaries (potential rootkit install):
find / -mtime -7 -perm /4000 -type f -ls 2>/dev/null
# Recently created/modified files in web directories (web shells):
find /var/www /srv/www /usr/share/nginx /opt/*/web -mtime -30 \
-name '*.php' -o -name '*.py' -o -name '*.pl' -o -name '*.jsp' \
2>/dev/null | xargs ls -la 2>/dev/null
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Phase 3: Log Analysis
Authentication log analysis:
# Ubuntu/Debian:
grep 'sshd' /var/log/auth.log | tail -200
# Look for: Accepted password/publickey (successful logins), Failed password (brute force)
# Look for: sudo commands executed by attacker
# Find all successful SSH logins:
grep 'Accepted' /var/log/auth.log | \
awk '{print $1, $2, $3, $9, $11}' | \
sort -u
# Output: Month Day Time username source_IP
# Find sudo command execution:
grep 'sudo' /var/log/auth.log | grep -v 'pam_unix'
# Shows: which user ran which command as sudo
# RHEL/CentOS:
grep 'sshd\|sudo' /var/log/secure | tail -200
Web server log analysis (web shell detection):
# Apache access log: look for POST requests to PHP files in unusual locations:
grep 'POST' /var/log/apache2/access.log | \
awk '{print $1, $6, $7, $9}' | \
grep -E '\.php|\.(cgi|asp|aspx|sh)' | \
tail -100
# Nginx access log: look for command execution patterns in URLs:
grep -E '(cmd=|exec=|system\(|passthru|shell_exec)' \
/var/log/nginx/access.log | tail -50
# Find the first time a suspicious PHP file was requested:
awk '{print $7}' /var/log/nginx/access.log | \
grep -o 'wp-content.*\.php' | sort | uniq -c | sort -rn | head -20
# High-count PHP files in unexpected locations = web shell candidates
# Look for common web shell user agents:
grep -E '(python|curl|wget|Go-http|libwww-perl)' /var/log/nginx/access.log | \
grep 'POST' | tail -50
# Web shell interactions often use scripted user agents
Detect log tampering:
# Check if auth.log has gaps (attacker may have deleted entries):
ls -lah /var/log/auth.log* # Check rotation and size
stat /var/log/auth.log # Mtime = last write time
# auditd can detect log modification if properly configured:
ausearch -k log_modification 2>/dev/null
# Check /var/log/lastlog for login history:
lastlog | head -30 # Shows last login per account
# Check utmp/wtmp for session history:
last -F | head -50 # -F shows full timestamps
Phase 4: Web Shell and Rootkit Detection
Find web shells with content scanning:
# Look for PHP code patterns common in web shells:
grep -r --include='*.php' -l 'eval\|base64_decode\|system\|passthru\|shell_exec\|exec' \
/var/www/ 2>/dev/null
# More specific: eval(base64_decode(...)) is the classic web shell pattern:
grep -r --include='*.php' 'eval(base64_decode' /var/www/ 2>/dev/null
# Find PHP files with suspicious permissions or in unexpected locations:
find /var/www /tmp /var/tmp -name '*.php' -ls 2>/dev/null
find /var/www -name '*.php' -perm /o+w -ls 2>/dev/null
# World-writable PHP files = extremely suspicious
# Scan with Linux Malware Detect (LMD) or ClamAV:
clamscan -r --infected /var/www/ 2>/dev/null
# Check for hidden files in web directories:
find /var/www -name '.*' -not -name '.htaccess' -ls 2>/dev/null
# Hidden files (starting with .) in web root = suspicious
Rootkit detection:
# Install and run rkhunter:
apt install rkhunter -y
rkhunter --update && rkhunter --check --sk
# Install and run chkrootkit:
apt install chkrootkit -y
chkrootkit
# Check for LD_PRELOAD rootkits (intercept shared library calls):
cat /etc/ld.so.preload # Should normally be empty
env | grep LD_PRELOAD # Should normally be empty
# Check for hidden processes using /proc vs ps:
ls /proc/ | grep -E '^[0-9]+$' > /tmp/proc_pids.txt
ps aux | awk '{print $2}' | tail -n +2 | sort -n > /tmp/ps_pids.txt
diff /tmp/proc_pids.txt /tmp/ps_pids.txt
# PIDs in /proc but not in ps = hidden by rootkit
The bottom line
Linux server IR follows this order: (1) collect volatile evidence first: w, ps auxf, ss -tnpu, lsof -i: before any other action; (2) examine persistence: SSH authorized_keys, cron, systemd units, bash profiles; (3) analyze logs: auth.log for SSH/sudo activity, web server logs for POST requests to PHP files; (4) find web shells: grep -r 'eval(base64_decode' /var/www/; (5) detect rootkits: rkhunter, chkrootkit, compare /proc vs ps for hidden processes. Hash every suspicious file and check VirusTotal before deleting. Always collect evidence before remediation to preserve the ability to understand the full attack chain.
Frequently asked questions
What should you do first when you suspect a Linux server is compromised?
Collect volatile evidence before doing anything else: run `w` (current users), `ps auxf` (all processes with arguments), `ss -tnpu` (all network connections with process names), and `lsof -i -n -P` (all open network files). This data is lost on reboot. Then capture the process executable hashes to a file for VirusTotal verification. Only after volatile collection should you examine disk artifacts like cron jobs, authorized_keys, and log files.
How do you find a web shell on a compromised Linux server?
Run `grep -r --include='*.php' -l 'eval\|base64_decode\|system\|shell_exec' /var/www/` to find PHP files containing web shell patterns. Specifically `eval(base64_decode(...))` is the most common web shell signature. Also check: `find /var/www -mtime -30 -name '*.php' -ls` for recently created PHP files, `find /var/www -name '.*' -ls` for hidden files in web directories, and examine web server access logs for POST requests to PHP files from scripted user agents (curl, python, wget).
How do you investigate initial access on a compromised Linux server?
Linux initial access investigation sequence: (1) Check auth.log or /var/log/secure for SSH brute force and successful logins from external IPs before the compromise window. (2) Review web server access logs (Apache: /var/log/apache2/access.log, Nginx: /var/log/nginx/access.log) for exploit signatures: large POST bodies to PHP files, path traversal sequences, SQL injection patterns. (3) Check /var/log/syslog for sudo executions and cron job additions. (4) Run 'last -F' to list all user logins with timestamps and source IPs. (5) Check bash_history for all user accounts: 'cat /home/*/.bash_history /root/.bash_history'. Note: sophisticated attackers clear bash_history — absence of history is itself suspicious for accounts that were logged into.
How do you identify and remove Linux rootkits after a server compromise?
Linux rootkit detection: run rkhunter ('rkhunter --check') and chkrootkit to scan for known rootkit signatures and system binary modifications. Both compare checksums of system binaries against expected values. For kernel-level rootkits: check for unexpected kernel modules with 'lsmod' and 'modinfo' on suspicious entries; compare running modules against a baseline. If a rootkit is confirmed, do NOT trust any tools on the compromised system — the attacker may have replaced ls, find, netstat, and ps with versions that hide the rootkit. Use a bootable live CD or trusted binaries from a clean system. For compromised servers, the safest remediation is wiping and rebuilding from a known-good image rather than attempting in-place cleaning.
What Linux persistence mechanisms should I check after a compromise?
Check all Linux persistence locations: crontab for all users ('crontab -l -u <user>' for each user, plus /etc/cron.d/, /etc/cron.daily/, /etc/cron.hourly/); systemd services (ls /etc/systemd/system/*.service and systemctl list-units --type=service -- compare against baseline); /etc/rc.local and /etc/init.d/ for legacy startup scripts; /etc/profile, /etc/bashrc, and ~/.bashrc/.bash_profile for shell initialization persistence; authorized_keys files for all user accounts (attackers add their SSH key for re-entry); and LD_PRELOAD in /etc/ld.so.preload (a library listed here loads before all other libraries -- used for shared library injection persistence).
How do you preserve forensic evidence from a compromised Linux server for legal proceedings or external forensic analysis?
Forensic preservation requires maintaining chain of custody from collection through storage. Before any remediation: acquire a full memory image using avml ('avml /mnt/external/memory.lime') or the LiME kernel module, run from read-only media or a trusted remote tool to avoid contaminating the system. Create bit-for-bit disk images using dc3dd with hash verification: 'dc3dd if=/dev/sda of=/mnt/external/sda.img hash=sha256 log=/mnt/external/sda.log'. Calculate SHA256 hashes of every collected artifact and record them in a signed chain-of-custody document at the time of collection. Store evidence on write-protected external media or an external system with tamper detection. For cloud instances: take a VM snapshot before any remediation -- the snapshot captures disk state and is a valid forensic artifact for most legal proceedings. Critical constraint: do not run rkhunter, clamscan, or any tool that writes to the system disk before imaging, as modifications after the incident affect evidence integrity and may undermine legal proceedings.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
