SUID, SGID and File Capabilities: Linux Privilege Backdoors
SUID/SGID bits and file capabilities (security.capability) on Linux: how attackers plant root backdoors and how to baseline them against packages.
- Location
- Inode mode bits (04000, 02000) and the security.capability extended attribute, anywhere on the file system
- Proves
- Which executables run with elevated privileges regardless of who starts them, and whether any were added or altered outside the package manager
- Timestamps
- Setting a bit or capability updates the inode ctime only; mtime and birth time come from the copy or install
- Access
- Readable by any user with stat/getcap; root to set
- Retention
- Persistent until the file is replaced or the bit removed; package updates reset package-owned files
- Collection
- UAC, Velociraptor, find, getcap -r
Tools
Compare all tools- findCLI · built into Linux
- getcapCLI · built into Linux
- getfattrCLI · built into Linux
- statCLI · built into Linux
- rpmCLI · built into Linux
- dpkgCLI · built into Linux
- debsumsCLI · open source
What it is
A set-user-ID (SUID) executable runs with the privileges of its owner, and a set-group-ID (SGID) executable with those of its group. passwd, sudo, su, mount and pkexec are SUID root by design. File capabilities are the finer-grained alternative: instead of full root, a binary receives specific powers such as cap_net_raw or cap_setuid, stored in the security.capability extended attribute.
Both are simple, durable privilege-escalation backdoors (MITRE ATT&CK T1548.001, Setuid and Setgid). A root attacker who wants a way back in as any user can copy bash to a hidden path and set the SUID bit, or give python3 the cap_setuid capability. Any later low-privileged shell becomes root in one command. The same bits also show which legitimate binaries a local exploit may have targeted.
Where it lives
| Item | Where | Notes |
|---|---|---|
| SUID bit | Inode mode 04000, shown as s in the owner execute position (-rwsr-xr-x) | S means the bit is set without execute |
| SGID bit | Inode mode 02000, s in the group position | On directories, new files inherit the group instead |
| File capabilities | xattr security.capability on the inode | Shown by getcap; survive only on file systems with xattr support |
| Mount options | /etc/fstab, /proc/self/mountinfo | nosuid ignores both SUID/SGID bits and file capabilities on that mount |
| Package baseline | dpkg /var/lib/dpkg/info/*.list and md5sums; RPM database under /var/lib/rpm/ or /usr/lib/sysimage/rpm/ | RPM records expected modes; dpkg does not |
What it proves
- A given executable runs with owner or group privileges, or with specific capabilities, for every user who can execute it.
- Whether the file belongs to a package and still matches it (RPM checks mode, owner, group and capabilities; dpkg only content hashes).
- When the privilege was most likely granted: the inode ctime of a file whose mtime is older.
- It does not prove the backdoor was used. Look for the corresponding execution in audit.log (
euid=0with a non-zerouidandauid) or in shell history.
Key fields
$ stat -c '%A %U:%G %i %n' /var/tmp/.font-unix/dbus-launch
-rwsr-xr-x root:root 1311013 /var/tmp/.font-unix/dbus-launch
$ getcap -r / 2>/dev/null
/usr/bin/ping cap_net_raw=ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_admin,cap_net_bind_service=ep
/opt/tools/python3.11 cap_setuid=ep
| Element | Meaning |
|---|---|
s in owner/group bits | SUID/SGID |
| Owner | For SUID, the identity gained; root in almost every backdoor |
cap_*=ep | Capability in the effective and permitted sets at exec |
cap_setuid, cap_setgid, cap_dac_override, cap_dac_read_search, cap_sys_admin, cap_sys_ptrace, cap_sys_module | Each is effectively root when given to an interpreter or shell |
cap_net_raw, cap_net_bind_service, cap_net_admin | Common legitimate grants (ping, mtr, gst-ptp-helper, some servers) |
Timestamps
chmod u+s and setcap change only the inode ctime. A copied shell (cp /bin/bash /var/tmp/.x) gets a fresh mtime and birth time unless cp -p or touch -r was used, and even then ctime and ext4 crtime reveal when the copy was made (see ext4 timestamps). For package-owned files, compare ctime with the install or upgrade time of the owning package in the package logs.
Retention
The bits and xattrs stay until someone changes them or the file is replaced. A package upgrade replaces a package-owned binary and removes a capability an attacker added to it; unowned copies are never touched. Copying a file without --preserve=xattr (or tar without --xattrs) silently drops capabilities, so collect with tools that preserve them or record them first.
Collection
# Live or on a mounted image: SUID/SGID inventory with times, one file system at a time
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f \
-printf '%m\t%u:%g\t%s\t%T+\t%C+\t%p\n' 2>/dev/null > /media/ir/suid_sgid.tsv
getcap -r / 2>/dev/null > /media/ir/capabilities.txt
# Offline image (paths relative to the mount)
getfattr -R -m '^security\.capability$' -d /mnt/evidence 2>/dev/null
UAC's suid artifact lists SUID files; add SGID and capabilities with your own commands. Velociraptor's Linux.Sys.SUID hunts SUID binaries across a fleet.
Parsing
R=/mnt/evidence
# RPM: mode (M), owner (U), group (G) or capability (P) changes on package files
rpm --root "$R" -Va 2>/dev/null | grep -E '^.{1}M|^.{5}U|^.{6}G|^.{8}P'
# Debian: SUID/SGID files not owned by any package
find "$R" -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null | while read -r f; do
dpkg --root="$R" -S "${f#$R}" >/dev/null 2>&1 || echo "unowned: ${f#$R}"; done
# Shell or interpreter copies: hash every SUID file and compare with known shells
find "$R" -xdev -perm -4000 -type f -exec sha256sum {} + | sort > suid_hashes.txt
sha256sum "$R"/usr/bin/bash "$R"/usr/bin/dash "$R"/usr/bin/python3* 2>/dev/null
Investigator tips
- Build a baseline from a clean host of the same release and diff. Legitimate SUID lists are short (roughly 15 to 30 files) and stable.
- Any SUID or SGID file in
/tmp,/var/tmp,/dev/shm, home directories or web roots is a red flag;nosuidmounts neutralize it, so check whether /tmp and /dev/shm are mounted that way. - A SUID file whose hash equals
bash,dash,sh,python,perl,find,vimorenvis a backdoor, whatever its name. GTFOBins lists binaries that give a shell when SUID or capable. - Capabilities on interpreters (
python3,perl,node,ruby) or ontar,gdbandvimare rarely legitimate. Check for them explicitly because many triage scripts only look for SUID bits. - Kernel exploits sometimes set the SUID bit on a helper as their final step. A new SUID file created at the same time as a crash in the kernel log is worth correlating.