CVE-2021-3156 sudo提权漏洞-堆溢出调试分析

0x00 简介

sudo的一个漏洞:影响范围1.8.2 – 1.8.31p2 以及 1.9.0 -1.9.5p1。非root可以使用sudo来以root的权限执行操作。漏洞本质是由于sudo错误的转义了\过滤掉了截断符号,最终导致了一个堆溢出漏洞。

提权方式:最终通过覆写加载的动态库为自编译的危险文件,实现提权

详细介绍:
在 service_user结构体中指定了要动态加载的动态链接库,如果能够修改 service_user->name,那么就能指定加载伪造的动态链接库。
在一个 service_user结构体前面释放一个堆块,然后 分配 user_args分配到该堆块,随后使用堆溢出覆盖 service_user结构体。在 nssload_libray中,构造了满足调用新动态链接库的条件,所以会通过 ni->name构造动态链接库的名字 shlib_name为 libnss_X/POP_SH3LLZ .so.2。最终会通过 __libc_dlopen(shlib_name)打开。而 libnssX/POP_SH3LLZ .so.2中只含有一个 init函数,该函数的作用就是id(0)调用 execv(‘/bin/sh’),自此完成了提权。

0x01 背景

之前爆出的漏洞,这里对漏洞进行调试分析。
CVE-2021-3156漏洞可以在类uninx中非root可以使用sudo来以root的权限执行操作。漏洞本质是由于sudo错误的转义了\导致了一个堆溢出漏洞。

漏洞影响范围是1.8.2 – 1.8.31p2 以及 1.9.0 -1.9.5p1

本地环境:

0x02 代码分析

1.8.21p2

sudo.c文件:

sudo加上 -s选项会设置 MODE_SHELL,加上 -i选项会设置 MODE_SHELL和 MODE_LOGIN_SHELL。

parse_args.c文件的parse_args函数中:

在 main()(sudo.c)函数中调用了parse_args(),parse_args()会连接所有命令行参数,并加反斜杠来重写 argv。

在 set_cmnd()函数中,首先根据参数使用 strlen()函数计算了参数的 size,再调用 malloc()函数分配了 size大小的堆空间 user_args 。随后判断是否开启了 MODE_SHELL,如果开启了将会连接命令行参数并存入堆空间 user_args。

假如a[0] = “", a[1]= “\x00”, 那么满足if(from[0]==”\“&&isspace(unsigned char)from[1])

  while (*from) {
            if (from[0] == '\\' && !isspace((unsigned char)from[1]))   // a[0]是反斜杠,a[1]是空格则地指针自增1
                from++;    // from加一则指向null结束符

null结束符号则会被拷贝到user_args堆缓冲区中,from加1,from指向null结束符后面第一个字符。随后会继续将后面的字符拷贝到user_args,发生堆溢出。

0x03 触发漏洞

在 parse_args()会对启用了 -s或 -i的 MODE_SHELL和 MODE_RUN 的 sudo的参数加上 反斜杠 转义。

而 set_cmnd()函数中触发堆溢出前,会判断是否启用了 MODE_SHELL 和 MODE_RUN、MODE_EDIT、MODE_CHECK 中的一个。那么就存在一个矛盾,如果要触发漏洞就需要启用 MODE_SHELL,但是如果启用了 MODE_SHELL,在 parse_args()函数中就会对所有参数转义,触发漏洞的 \,将会被转义为 \,这样就无法触发漏洞了。

// parse_args
if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { // 检查是否开启了-s或-i的MODE_SHELL
转义 "\"==> "\\"
// soduers_policy_main

if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { // 判断是否开启MODE_SHELL
漏洞代码

那么想要成功执行堆溢出就只能设置flags=MODE_SHELL/MODE_LOGIN_SHELL的条件下不设置mode=MODE_RUN以免转移代码的执行。所以我们只能设置MODE_RUN来避免转义代码的执行,同时为了执行漏洞代码,又需要设置MODE_EDIT或MODE_CHECK。

我们看到如果设置这两个参数和MODE_SHELL/MODE_LOGIN_SHELL,在后续检测会退出

但是如果用sudoedit:

发现没有设置valid_flags,那么就可以顺利通过检查执行漏洞代码。

0x04 漏洞调试

1.首先使用如下命令运行 exp:

sudo gdb --args ./sudo-hax-me-a-sandwich 0

2.随后,在 execve下断点:

catch exec

gdb会断在execve函数,我们下断点b setlocale,在继续运行,此时就会停在setlocale函数。该函数是我们在执行sudo最开始会调用的。finish后就可以进入sudo的main函数中。
sudo.c

当main函数加载了sudoer.so后对malloc下断点得到heap的地址

堆溢出:

0x05 提权方法

覆盖动态链接库地址

在 service_user结构体中指定了要动态加载的动态链接库,如果能够修改 service_user->name,那么就能指定加载伪造的动态链接库。而 nss_load_library函数就是加载动态链接库的函数,其会调用 __libc_dlopen打开动态库。

了解sevice_library结构体和service_user结构体

typedef struct service_library
{
  /* Name of service (`files', `dns', `nis', ...).  */
  const char *name;
  /* Pointer to the loaded shared library.  */
  void *lib_handle;
  /* And the link to the next entry.  */
  struct service_library *next;
} service_library;


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函数

第一个地方要求:ni->library=null
第二个地方可以设置ni->library为name,所以不会退出,初始直接覆盖为null(运行完既为fake libc name的地址)
第三个地方要求:ni->library->lib_handle=null 才能加载新库

第四个地方要求:伪造的库文件名字必须是libnss_xxx.so

第五个地方调用_libc_dlopen来加载动态库

那么我们要思考如何通过一个堆溢出覆盖service_user结构体,思路大致是在分配到service_user这个结构体前面的堆块,通过溢出来修改service_user结构体。

堆排布

由于我们想通过user_args来覆盖到service_user那么需要实现一个好的堆排布,否则距离太远导致覆盖到重要数据可能会令程序崩溃。在这里我们使用setlocale来分配释放堆块,分配释放堆块后由于setlocale在sudo前,那么在分配service_user前可以提前占个堆块的位置,那么就可以在分配user_args时分配在service_user堆地址前,就可以成功溢出到该结构体,修改name为指定lib。

0x555555587360是我们下个分配的堆块

此时我们看看附近的堆排布:

接下来查看service_user结构体位置

search -s systemd [heap]

我们得知在可以溢出的堆块user_args下面最近的是0x5555555873e0位置的service_user结构体

将离 user_args最近的 service_user结构体覆盖后,程序会调用 getgrgid()函数,最后去调用 nss_load_library

溢出后堆内存布局:

我们成功将ni->name改为X/P0P_SH3LLZ_

调用nss_load_library

我们在nss_load_library下个断点:

第一次调用nss_load_library:

被覆盖的那个service_user作为参数

可以看到ni->library被设置为fake libc name

可以看到成功加载了fake libc

总结

1.利用setlocale进行堆占位(堆排布),为了可以分配user_args溢出后面的service_user
2.堆溢出覆盖library为null,name为指定libc名字


 Previous
D-Link DIR-859 CVE-201917621-未授权命令执行漏洞分析 D-Link DIR-859 CVE-201917621-未授权命令执行漏洞分析
0x01 漏洞简介D-Link DIR-859设备LAN层中出现未经身份验证的命令执行漏洞,主要是在ssdpcgi函数中发现了该漏洞,且因为SSDP协议缘故,该漏洞利用无须通过认证 漏洞起因主要是因为环境变量没有进行字符过滤,即使这个函数之
Next 
思科路由器RV110W-CVE-2020-3331/CVE-2020-3323漏洞复现 思科路由器RV110W-CVE-2020-3331/CVE-2020-3323漏洞复现
前言强网杯Realworld赛题,要求挖掘并利用CISCO RV110W-E-CN-K9(固件版本1.2.2.5)中的漏洞,获取路由器的Root Shell。攻击演示时的目标设备端口只开启了443端口的https服务,且不知道路由器的Web
  TOC