Linux persistence and privilege-escalation triage against a mounted forensic image. Six persistence families, four privesc paths, ATT&CK techniques inline. Image must be mounted read-only first - use Mount Disk Images workflow. Examples assume mount point /mnt/img.
Account-based persistence
[T1078] Valid Accounts
[T1136] Create Account
diff /mnt/img/etc/passwd /mnt/img/etc/passwd- # detect new accounts via daemon backup diff
diff /mnt/img/etc/group /mnt/img/etc/group- # detect new groups
diff /mnt/img/etc/shadow /mnt/img/etc/shadow- # detect new shadow rows
stat /mnt/img/etc/{passwd,group,shadow} # placement timestamp - useradd writes all 3 atomically (<30ms)backup files (passwd-, group-, shadow-) are managed by useradd/usermod - they hold the previous version. diff against them surfaces every account change in one pass.
shadow password field decoder:
| Field 2 value | Means |
|---|---|
!! or ! | locked, no password ever set - cannot password-auth |
* | locked by admin |
$6$<salt>$<hash> | SHA-512 hash, can password-auth |
$2y$<rounds>$<hash> | bcrypt, can password-auth |
awk -F: '$2 ~ /^\$/ {print $1}' /mnt/img/etc/shadow # accounts with real passwordsSSH authorized_keys persistence
[T1098.004] Account Manipulation: SSH Authorized Keys
find /mnt/img/home /mnt/img/root -name authorized_keys -ls
cat /mnt/img/home/*/.ssh/authorized_keys
cat /mnt/img/root/.ssh/authorized_keysone find pass closes the family host-wide. Multiple keys in one file = multiple operators or an attacker keeping a backup channel.
Cron-based persistence
[T1053.003] Scheduled Task/Job: Cron
cat /mnt/img/etc/crontab
ls /mnt/img/etc/cron.d/
cat /mnt/img/etc/cron.d/*
ls /mnt/img/etc/cron.{hourly,daily,weekly,monthly}/
ls /mnt/img/var/spool/cron/
cat /mnt/img/var/spool/cron/*distro split for user crontabs:
| Family | Path |
|---|---|
| Debian / Ubuntu | /var/spool/cron/crontabs/<user> |
| RHEL / CentOS | /var/spool/cron/<user> (no subdir) |
journalctl --root=/mnt/img -t CRON --since '<incident-date>' # actual cron execution evidenceverify cron entries actually executed - cron fails silently when the command path does not exist. parse the command line as /bin/sh -c would and confirm the executable exists.
systemd persistence
[T1053.006] Scheduled Task/Job: Systemd Timers
[T1543.002] Create or Modify System Process: Systemd Service
find /mnt/img/etc/systemd /mnt/img/usr/lib/systemd /mnt/img/lib/systemd -name '*.service' -o -name '*.timer' | xargs stat -c '%y %n' | sort -r | head -20stock CentOS 7 ships ~430 unit files. mtime-sort cuts the read to 20. Anything with mtime inside the adversary window is the lead.
grep -rE 'ExecStart=' /mnt/img/etc/systemd/system/ /mnt/img/usr/lib/systemd/system/read every ExecStart= for non-distro-standard binaries (anything outside /usr/bin, /usr/sbin, /usr/lib, /sbin).
Init / boot script persistence
[T1037] Boot or Logon Initialization Scripts
cat /mnt/img/etc/rc.local # follow symlink on RHEL family - real file at /etc/rc.d/rc.local
cat /mnt/img/etc/rc.d/rc.local
ls -la /mnt/img/etc/init.d/
ls -la /mnt/img/etc/rc{0..6}.d/look for SXX symlinks in rcN.d/ that don’t point to standard init.d files. Read rc.local body against the stock template - usually empty or contains a single exit 0.
Shell-config persistence
[T1546.004] Event Triggered Execution: Unix Shell Configuration Modification
cat /mnt/img/etc/profile
cat /mnt/img/etc/bashrc
ls /mnt/img/etc/profile.d/
cat /mnt/img/etc/profile.d/*.sh
find /mnt/img/home /mnt/img/root \( -name '.bashrc' -o -name '.bash_profile' -o -name '.bash_logout' -o -name '.zshrc' \) -lsstock skel sizes are stable across distro releases. Compare against expected:
| File | Stock size (CentOS 7 user) | Stock size (root) |
|---|---|---|
.bashrc | 231 B | 176 B |
.bash_profile | 193 B | 176 B |
.bash_logout | 18 B | 18 B |
deviation in size or mtime past distro-build date is the lead. Watch for backgrounded commands ending in & that detach an attacker process.
Web shell persistence
[T1505.003] Server Software Component: Web Shell
find /mnt/img/var/www /mnt/img/srv -name '*.php' -newer /tmp/before-incident -ls
find /mnt/img/var/www -type f \( -name '*.php' -o -name '*.jsp' -o -name '*.aspx' \) | xargs stat -c '%y %n' | sort -r | head -20cross-reference web-shell hits with [[Web Log Triage]] access-log queries. A shell file on disk + access-log hit = confirmed deployment.
Privilege escalation taxonomy
sudo + sudoers.d drop-ins
[T1548.003] Abuse Elevation Control Mechanism: Sudo and Sudo Caching
cat /mnt/img/etc/sudoers
ls /mnt/img/etc/sudoers.d/
cat /mnt/img/etc/sudoers.d/*
grep -E 'NOPASSWD|ALL.*ALL' /mnt/img/etc/sudoers /mnt/img/etc/sudoers.d/*sudoers.d/ drop-ins are read by visudo at parse time but invisible from a cat /etc/sudoers alone. Always enumerate the directory.
Privileged group membership
[T1098] Account Manipulation
grep -E '^(wheel|sudo|docker|adm|root):' /mnt/img/etc/group
diff /mnt/img/etc/group /mnt/img/etc/group-distro split: Debian/Ubuntu use sudo group, RHEL/CentOS use wheel. docker group membership is a privesc path on hosts running Docker - members can mount host paths into containers as root.
Setuid / setgid baseline diff
[T1548.001] Abuse Elevation Control Mechanism: Setuid and Setgid
find /mnt/img -xdev \( -perm -4000 -o -perm -2000 \) -type f | xargs stat -c '%y %n' | sort -r | head -30stock CentOS 7 has 25 setuid binaries. Stock Ubuntu LTS has ~30. Any binary outside the baseline path set (/usr/bin, /usr/sbin, /bin, /sbin) or with mtime inside the adversary window is the lead.
Shadow access (credential dumping)
[T1003] OS Credential Dumping
stat /mnt/img/etc/shadow # atime within adversary window = read by attackerBirth on shadow tracks the last useradd/usermod/passwd (rename-on-write atomic replace). atime reflects last read - hits inside the adversary window mean the attacker copied the hashes for offline cracking.
Triage decision flow
graph TD mounted["image mounted ro at /mnt/img"] --> accounts["account diff<br/>passwd/group/shadow vs backups"] accounts -->|"new account"| stat_acc["stat for placement timestamp<br/>then SSH key + sudoers + group check"] accounts -->|"clean"| ssh["SSH authorized_keys host-wide find"] ssh --> cron["cron audit<br/>5 paths + journalctl -t CRON"] cron --> sysd["systemd unit mtime sort"] sysd --> init["init.d / rc.local diff"] init --> shell["shell config + skel size diff"] shell --> web["web shell hunt<br/>cross-ref Web Log Triage"] web --> privesc["privesc: sudoers / groups / setuid / shadow atime"]
Pitfalls
- dormant accounts treated as “no finding”. account creation = persistence regardless of operational state. report dormant accounts with a “no current operational role” qualifier and the placement timestamp.
- assuming a cron entry actually executes. parse the command line as
/bin/sh -cdoes. verify the executable path exists. cross-checkjournalctl -t CRONfor execution evidence. - stopping at the persistence artefact and not investigating the source. a public GitHub repo clone = T1588.002 Obtain Capabilities: Tool. Separate finding from the persistence + execution techniques.
-aneweragainst anoatime-mounted filesystem. atime is not updated, so-anewerreturns nothing. Use-newer(mtime) or-cnewer(ctime) instead.- reference-file ctime trap.
touch -t YYYYMMDDhhmm /tmp/markerupdates marker’s mtime AND ctime.find -cnewer /tmp/markerthen includes the marker creation event itself. Use a marker on a separate filesystem or pre-existing file with known timestamps. find /without-xdev. recurses into/procand/sysand produces enormous synthetic output. Always-xdevor explicit excludes./etc/passwdmtime ambiguity. shadow-utils use lock-read-write-rename, so/etc/passwdBirth tracks the last operation, not the file’s first appearance. Use thepasswd-/group-/shadow-backup files for history.- stat-ing a symlink instead of its target. use
stat -Lor the target path directly./etc/rc.localis commonly a symlink on RHEL. - regex on /etc/shadow hash field.
[^!*]matches one character. Real hashes are 50+. Useawk -F: '$2 ~ /^\$/ {print $1}'. - sudoers.d/ drop-ins invisible from /etc/sudoers. always enumerate the directory.
- sub-technique vs parent technique discipline. T1098.004 (SSH keys) is a sub-technique of T1098 (Account Manipulation). Tag the most specific applicable technique.
links:
Field Manual | Mount Disk Images | Filesystem Metadata | Log Triage | Web Log Triage