Skip to content

Back to the catalog

dfir.chwriteup

Linux Capabilities Revisited

A DFIR.ch technical walkthrough of Linux POSIX capabilities: how to weaponize cap_setuid on a binary like /usr/bin/python3.12 with setcap to create a stealthy root-spawning backdoor without touching SUID bits, then how defenders can hunt for these via getcap -r, /proc status Cap* fields, LinPEAS, and an Elastic detection rule that flags setcap process execution. It also explains where capabilities live on disk (the security.capability extended attribute on the inode) and how to inspect/remove them with getfattr and setcap -r.

TL;DR Shows how to backdoor a binary with cap_setuid via setcap and how to hunt/detect it with getcap, LinPEAS, and an Elastic setcap detection rule.
Document

What it is

A DFIR.ch walkthrough of using Linux POSIX capabilities — specifically cap_setuid — as a SUID-free privilege escalation backdoor, plus the enumeration commands to hunt it. The concrete artifact is a cap_setuid=ep capability set on the Python binary, which lets any user spawn a UID 0 shell without a SUID bit or a modified binary.

How it works

setcap writes capability flags into the file's inode extended attributes. cap_setuid permits arbitrary UID manipulation, including setting UID to 0. The +ep suffix marks the capability Effective (activated) and Permitted (allowed). Once set, an unprivileged user calls Python's os.setuid(0) and gets root.

# setcap cap_setuid+ep /usr/bin/python3.12
$ /usr/bin/python3 -c 'import os;os.setuid(0);os.system("/bin/bash")'

What to do

Enumerate every binary with capabilities — this is the parallel to SUID hunting, and ls will not show these flags:

# getcap -r / 2>/dev/null

Inspect a running process's capabilities:

# cat /proc/<PID>/status | grep Cap
# getpcaps <PID>
# capsh --decode=0000000000000080

The status fields decode as: CapInh inherited, CapPrm permitted, CapEff effective, CapBnd bounding set, CapAmb ambient.

Remove a planted capability:

# setcap -r /usr/bin/python3.12

Because the flags live in inode extended attributes (same location as ACLs and setuid/setgid bits), dump all extended attributes to audit a file directly:

# getfattr -d -m -

The relevant extended attribute name is security.capability. Check /proc/sys/kernel/cap_last_cap for the highest capability number the kernel knows.

Detection

The Elastic rule "Process Capability Set via setcap Utility" fires on setcap execution, excluding a fixed set of legitimate parents:

process where host.os.type == "linux" and event.type == "start" and event.action in ("exec", "exec_event", "start") and
process.name == "setcap" and not (
  process.parent.executable == null or
  process.parent.executable : ("/var/lib/dpkg/*", "/var/lib/docker/*", "/tmp/newroot/*", "/var/tmp/newroot/*") or
  process.parent.name in ("jem", "vzctl")
)

Note this detects the setting of the capability, not later abuse of an already-planted one — an attacker who set the flag before logging began, or via a whitelisted parent path, evades it. Combine with periodic getcap -r / sweeps.

Organisations named here

  • cap_setuid
  • security.capability

Technologies named here

  • capsh
  • Linux
  • Python
  • setcap
  • getcap
  • getpcaps
  • getfattr
  • LinPEAS
  • Elastic
  • Linux kernel
  • Elasticsearch

Techniques named here

  • Backdooring via file capabilities
  • Enumeration of capabilities