CVE-2021-3156(sudo Heap overflow sudo本地提权复现与分析)

CVE-2021-3156: Heap-Based Buffer Overflow in Sudo (Baron Samedit)
CVE-2021-3156简介:
sudo是一个几乎无处不在的实用程序,可用于主要的类Unix操作系统。通过利用此漏洞,任何未经授权的用户都可以使用默认sudo配置在易受攻击的主机上获得root权限。
Sudo是一个强大的实用程序,它包含在大多数基于Unix和Linux的操作系统中。它允许用户以另一个用户的安全权限运行程序。近10年来,这个漏洞本身一直隐藏在人们的视线中。
根据安全研究员[email protected]的报告:
受到威胁的系统有:
Ubuntu 18.04.5 (Bionic Beaver) - sudo 1.8.21, libc-2.27
Ubuntu 20.04.1 (Focal Fossa) - sudo 1.8.31, libc-2.31
Debian 10.0 (Buster) - sudo 1.8.27, libc-2.28

通过审计Sudo Mode模块代码:

571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
      
572         char **av, *cmnd = NULL; 
573         int ac = 1; 
... 
581             cmnd = dst = reallocarray(NULL, cmnd_size, 2); 
... 
587             for (av = argv; *av != NULL; av++) {
      
588                 for (src = *av; *src != '\0'; src++) {
      
589                     /* quote potential meta characters */ 
590                     if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$') 
591                         *dst++ = '\\'; 
592                     *dst++ = *src; 
593                 } 
594                 *dst++ = ' '; 
595             } 
... 
600             ac += 2; /* -c cmnd */ 
... 
603         av = reallocarray(NULL, ac + 1, sizeof(char *)); 
... 
609         av[0] = (char *)user_details.shell; /* plugin may override shell */ 
610         if (cmnd != NULL) {
      
611             av[1] = "-c"; 
612             av[2] = cmnd; 
613         } 
614         av[ac] = NULL; 
615  
616         argv = av; 
617         argc = ac; 
618     } 

稍后,在sudoers\u policy\u main()中,set\cmnd()将命令行参数连接到基于堆的缓冲区“user\u args”(第864-871行)中,并取消对元字符(第866-867行)的scape,“用于sudoers匹配和日志记录目的”:

819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
      
... 
852             for (size = 0, av = NewArgv + 1; *av; av++) 
853                 size += strlen(*av) + 1; 
854             if (size == 0 || (user_args = malloc(size)) == NULL) {
      
... 
857             } 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
      
... 
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                 } 
... 
884             } 
... 
886     } 

在第866行,“from[0]”是反斜杠字符,“from[1]”是参数的空终止符(即,不是空格字符);
在第867行,“from”递增并指向空终止符;
在第868行,空终止符被复制到“user\u args”缓冲区,“from”再次递增并指向空终止符后面的第一个字符(即,超出参数的边界);
第865-869行的“while”循环读取越界字符并将其复制到“user\u args”缓冲区。
换句话说,set\u cmnd()易受基于堆的缓冲区溢出的攻击,因为复制到“user\u args”缓冲区的越界字符未包含在其大小中(在第852-853行计算)。

但是,理论上,没有命令行参数可以以单个反斜杠字符结尾:如果设置了MODE\u SHELL或MODE\u LOGIN\u SHELL(第858行,这是到达易受攻击代码的必要条件),则设置MODE\u SHELL(第571行),parse\u args()已经转义了所有元字符,包括反斜杠(即。,它用第二个反斜杠逃过了每一个反斜杠)。
但实际上,set\u cmnd()中的易受攻击代码和
parse\u args()中的转义代码被稍微不同的条件包围:

819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
      
... 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
      
571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
      

我们的问题是:我们是否可以设置模式\u SHELL和模式\u EDIT或模式\u CHECK(以访问易受攻击的代码),而不是默认模式\u RUN(以避免转义代码)?

答案似乎是否定的:如果我们设置MODE_EDIT(-e option,第361行)或MODE_CHECK(-l option,第423行和第519行),那么parse_args()将MODE_SHELL从“valid_flags”(第363行和第424行)中删除,如果我们指定了MODE_SHELL(第532-533行)之类的无效标志,则返回错误:

358                 case 'e': 
... 
361                     mode = MODE_EDIT; 
362                     sudo_settings[ARG_SUDOEDIT].value = "true"; 
363                     valid_flags = MODE_NONINTERACTIVE; 
364                     break; 
... 
416                 case 'l': 
... 
423                     mode = MODE_LIST; 
424                     valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST; 
425                     break; 
... 
518     if (argc > 0 && mode == MODE_LIST) 
519         mode = MODE_CHECK; 
... 
532     if ((flags & valid_flags) != flags) 
533         usage(1); 

但是我们发现了一个漏洞:如果我们将Sudo作为“sudoedit”而不是“Sudo”执行,那么parse\u args()会自动设置模式\u EDIT(第270行),但不会重置“valid\u flags”,“valid\u flags”默认包括模式\u SHELL(第127行和第249行):

127 #define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL) 
... 
249     int valid_flags = DEFAULT_VALID_FLAGS; 
... 
267     proglen = strlen(progname); 
268     if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
      
269         progname = "sudoedit"; 
270         mode = MODE_EDIT; 
271         sudo_settings[ARG_SUDOEDIT].value = "true"; 
272     } 

因此,如果我们执行“sudoedit-s”,那么我们同时设置模式\u EDIT和模式\u SHELL(而不是模式\u RUN),我们就避免了转义代码,到达易受攻击的代码,并通过以单个反斜杠字符结尾的命令行参数使基于堆的缓冲区“user \u args”溢出:

sudoedit-s'\'`perl-e'print“A”x 65536'
malloc(): corrupted top size 
Aborted (core dumped) 

从攻击者的角度来看,这种缓冲区溢出非常理想,原因如下:
1) 攻击者控制可以溢出的“user\u args”缓冲区的大小(在第852-854行连接的命令行参数的大小);

2) 攻击者独立控制溢出本身的大小和内容(最后一个命令行参数后面紧跟着第一个环境变量,这些变量不包括在第852-853行的大小计算中);

3) 攻击者甚至可以将空字节写入溢出的缓冲区(每个以单个反斜杠结尾的命令行参数或环境变量都会将空字节写入第866-868行的“用户参数”)。

例如,在amd64 Linux上,下面的命令分配一个24字节的“user\u args”缓冲区(一个32字节的堆块)并用。
欢迎关注CSDN:知柯信息安全

你可能感兴趣的:(PWN,二进制漏洞挖掘,安全漏洞,安全,网络安全,操作系统)