Skip to content

PersistenceExecution

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
  • 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

ItemWhereNotes
SUID bitInode mode 04000, shown as s in the owner execute position (-rwsr-xr-x)S means the bit is set without execute
SGID bitInode mode 02000, s in the group positionOn directories, new files inherit the group instead
File capabilitiesxattr security.capability on the inodeShown by getcap; survive only on file systems with xattr support
Mount options/etc/fstab, /proc/self/mountinfonosuid ignores both SUID/SGID bits and file capabilities on that mount
Package baselinedpkg /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=0 with a non-zero uid and auid) 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
ElementMeaning
s in owner/group bitsSUID/SGID
OwnerFor SUID, the identity gained; root in almost every backdoor
cap_*=epCapability 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_moduleEach is effectively root when given to an interpreter or shell
cap_net_raw, cap_net_bind_service, cap_net_adminCommon 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; nosuid mounts 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, vim or env is a backdoor, whatever its name. GTFOBins lists binaries that give a shell when SUID or capable.
  • Capabilities on interpreters (python3, perl, node, ruby) or on tar, gdb and vim are 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.

See also