Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Linux capabilities split the historically monolithic power of root (UID 0) into roughly 40 discrete privileges — CAP_NET_BIND_SERVICE to bind ports below 1024, CAP_SYS_PTRACE to trace other processes, CAP_DAC_OVERRIDE to bypass file permission checks, CAP_SETUID to change UIDs, and so on — so that a program can be granted exactly the privilege it needs without being made fully root. This is a genuine security improvement over blanket SUID-root binaries, but the capability *sets* that control how a privilege propagates across execve() — permitted, effective, inheritable, ambient, and the per-file bounding set — are subtle enough that misconfiguring them routinely reintroduces the exact full-root exposure capabilities were meant to eliminate.
The two sets most relevant to both legitimate hardening and privilege escalation are inheritable and ambient. Inheritable capabilities have existed since capabilities were introduced but were historically almost useless without cooperating file capabilities on the target binary; ambient capabilities, added in Linux 4.3, changed that by letting a process grant capabilities that survive execve() into *any* non-SUID, non-file-capability child — which is exactly the property that makes both systemd‘s AmbientCapabilities= directive and a number of privilege-escalation techniques work.
Attack Prerequisites
Abusing capability inheritance for privilege escalation or persistence generally requires:
- A binary with a dangerous capability set via
setcap— most criticallycap_setuid+ep,cap_dac_override+ep, orcap_sys_admin+ep— reachable and executable by the attacker. - A process (often a shell) already holding the capability in its ambient or inheritable set, so that spawning a child normally propagates the privilege forward.
- Knowledge of which binaries have file capabilities set, discoverable via
getcap -r /— a scan every serious Linux privesc checklist runs early. - For the ambient-capability route specifically: the ability to run
capshor a small C/Python helper capable ofprctl(PR_CAP_AMBIENT, ...), or a systemd service already configured withAmbientCapabilities=.
How It Works
Every process has (at minimum) three capability sets: permitted (the ceiling of what it may ever hold), effective (what is currently active for permission checks), and inheritable (what should survive into a child process across execve()). Historically, inheritable capabilities only actually reached a child if the executed *file* also had matching capabilities in its own inheritable set (a property setcap can set, e.g. cap_net_raw+i), which meant an ordinary shell or unmarked binary would drop them — inheritable capabilities were “inherited” only by cooperating binaries.
Ambient capabilities remove that cooperation requirement. A capability placed in a process’s ambient set is automatically added to the permitted and effective sets of any child process on execve(), *provided* the executed file is not SUID, does not have file capabilities of its own, and the capability is also in the inheritable set. In effect: once a process has capability X ambient, every subsequent plain, unprivileged binary it execs also gets X — a shell spawned from a process with CAP_NET_RAW ambient can open raw sockets without ever being setcap‘d itself. systemd uses this legitimately via AmbientCapabilities=CAP_NET_BIND_SERVICE to let a non-root service bind port 80 without full root or a setcap on the binary.
This is powerful precisely because it is transitive and sticky: an ambient capability follows the process across fork()/exec() chains as long as nothing resets it (a setuid() call, or executing a file with its own file capabilities or SUID bit, both clear ambient capabilities as a safety measure). If the granted capability is CAP_SETUID or CAP_DAC_OVERRIDE, an ambient grant to an otherwise unprivileged shell is functionally equivalent to a path to root, because those capabilities let the holder become root or bypass the file permission checks that would otherwise stop it.
Vulnerable Code / Configuration
The classic enabling misconfiguration is setcap applied to an interpreter or generic utility rather than a narrow, purpose-built binary — this is the single most common Linux capability-based privesc finding:
$ getcap -r / 2>/dev/null
/usr/bin/python3.11 = cap_setuid+ep # VULNERABLE: interpreter, not
# a narrow tool, holds cap_setuid
BashA systemd unit granting a broad ambient capability to a service that does not need it is the equivalent misconfiguration in service form:
# /etc/systemd/system/collector.service — VULNERABLE
[Service]
User=svc-collector
AmbientCapabilities=CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/opt/collector/agent
# A non-root service can now trace and read the memory/files of
# effectively any process on the host.
INIEither misconfiguration is discoverable and, from an attacker’s perspective, directly weaponizable with the tool set below.
Walkthrough / Exploitation
Enumerate file capabilities across the filesystem first — this is a standard step in any Linux privesc checklist, alongside sudo -l and SUID enumeration:
getcap -r / 2>/dev/null
# /usr/bin/python3.11 = cap_setuid+ep
BashWith cap_setuid+ep on the Python interpreter, calling os.setuid(0) from within it succeeds even though the invoking user is unprivileged, because the effective capability set already contains CAP_SETUID:
python3.11 -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
Bashcapsh is the standard tool for inspecting and experimenting with capability sets directly, including simulating what a process would hold and dropping into a shell with a chosen ambient set for testing:
capsh --print # show current cap sets
capsh --caps="cap_setuid+eip cap_setuid=+i" \
--keep=1 --user=nobody --addamb=cap_setuid -- -c '/bin/bash -p'
BashWhere the vector is a systemd service with over-broad AmbientCapabilities=, the exploitation is simpler still — attach to or abuse the running service directly, since it already runs with, e.g., CAP_SYS_PTRACE against every other process on the host:
systemctl show collector.service -p AmbientCapabilities
# confirmed cap_sys_ptrace, cap_dac_read_search present
# from inside collector's context: ptrace-attach to a root process
# to read its memory, or read files via DAC_READ_SEARCH bypass
BashNote:
cap_setuid+epon an interpreter is strictly worse than the same capability on a small, single-purpose binary, because the interpreter can be pointed at *any* attacker-supplied code — a one-lineos.setuid(0)is trivial, but so is anything else the attacker wants to run as root.
Opsec:
getcap -r /is fast and leaves no trace beyond a filesystem read, which is why it appears in essentially every automated Linux enumeration script (LinPEAS, linux-smart-enumeration) — meaning defenders should assume any file capability grant will be found quickly by an attacker who gets a foothold, not treat it as obscure.
Detection and Defense
Capability hygiene is about minimizing what is granted and to what, then watching for drift:
- Never
setcapan interpreter or shell (python,perl,bash,node); scope file capabilities to narrow, single-purpose binaries only, and prefer the smallest capability that satisfies the need (cap_net_bind_serviceinstead ofcap_sys_admin, for example). - Audit
getcap -r /output as a routine fleet baseline and alert on new or unexpected file capabilities, especiallycap_setuid,cap_setgid,cap_dac_override,cap_dac_read_search, andcap_sys_admin. - Scope
AmbientCapabilities=in systemd units to the minimum needed, and pair it withCapabilityBoundingSet=so the ceiling matches the ambient grant exactly. - Use
capsh --printand/proc/<pid>/status(CapEff,CapAmblines) during incident response to see exactly what capabilities a suspicious process is holding. - Auditd rule on
setcap/setxattrforsecurity.capability:auditctl -a always,exit -F arch=b64 -S setxattr -F key=file_caps.
Real-World Impact
Misused file capabilities, particularly cap_setuid on interpreters, are one of the most frequently reported Linux local-privilege-escalation findings in penetration test reports and are a standard check in automated enumeration tooling; GTFOBins documents capability-based escalation paths for a range of common binaries. Ambient capabilities specifically are also central to how modern container runtimes and systemd services drop root safely, which means correct use is just as important operationally as catching the misuse.
Conclusion
Capabilities are a genuine improvement over blanket root, but inheritable and ambient sets exist specifically to propagate privilege across execve(), and that propagation is exactly what turns one overbroad setcap or AmbientCapabilities= line into a full root compromise. Scope every capability grant to the narrowest binary and the smallest set that does the job, never touch interpreters, and treat getcap -r / drift and unexpected CapAmb values as first-class detection signals.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments