CVE-2021-3156漏洞分析与利用

前言

这个CVE-2021-3156算是真正意义上的第一个洞吧,亲自上手调了下,当然关于参数到底怎么变化的还没有去搞清楚,不过光是理清调试方法以及调用链就花了好久,也积累了好多经验,希望后面复现其他能熟练点吧。

sudoedit

sudoedit允许用户安全地编辑文件,命令格式是sudoedit file,该命令首先创建你要编辑的文件的临时副本,然后搜索SUDO_EDITOR,VISUAL和EDITOR环境变量(按此顺序),以确定应调用哪个编辑器来打开刚刚创建的临时副本。 用户完成修改工作后,更改将复制回原始文件。

另外,sudoedit其实是sudo程序的软链接,但使用sudoedit启动会设置其mode为MODE_EDIT。

sudo版本

1
2
3
4
Sudo version 1.8.21p2
Sudoers policy plugin version 1.8.21p2
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.21p2

漏洞点

set_cmnd函数中,当from\\时,会自增,跳过NULL字符,继续复制envp参数,从而可构造堆溢出:

1
2
3
4
5
6
7
8
9
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}
*--to = '\0';

调试过程

第一次尝试cve,调试过程中遇到了好多问题。

第一个是去符号了,这个通过字符串等搜索可以确定基本的main函数和parse_args函数(0x11760)。

另外在代码中找不到set_cmnd函数的入口,尝试步过确定范围,最终在0x5674处发现程序报错:

image-20210311203033160

进入函数发现调用了/usr/lib/sudo/sudoers.so动态库中的函数,并通过字符串确定是sudoers_policy_check函数地址为0x171A0,sudoers_policy_main函数地址为0x1D8A0,不过set_cmnd内联在sudoers_policy_main函数里了,大概漏洞点在0x1DC68处。

另外可以通过在gdb中catch execc进入sudo程序的调试。

漏洞利用原理

总的利用过程为:利用堆溢出,修改堆中service_user结构中的动态库名,然后通过nss_load_library函数调用该动态库,最后提权。

service_user结构如下:

1
2
3
4
5
6
7
8
typedef struct service_user
{
struct service_user *next;
lookup_actions actions[5];
service_library *library;
void *known;
char name[0];
} service_user;

下面是nss_load_library函数的部分代码,可以看到将libc名称自动补全为"libnss_"+name+".so",然后载入内存。

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
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];

/* Construct shared object name. */
__stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
"libnss_"),
ni->name),
".so"),
__nss_shlib_revision);

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

注:在Linux中通过locale来设置程序运行的不同语言环境,LC_ALL=C.UTF-8@即去除所有本地化的设置,其中C是系统默认的locale,UTF-8表示字符集。

参考文章

CVE-2021-3156调试分析

CVE-2021-3156: Heap-Based Buffer Overflow in Sudo (Baron Samedit)

CVE-2021-3156 PoC