CVE-2021-3156 Linux sudo漏洞分析

2021-02-04 8,577

漏洞概述

Qualys研究人员发现了Linux sudo的安全漏洞。在类Unix 系统中,攻击者利用该漏洞可以让非特权用户在默认sudo 配置下获取root权限。

Sudo 是基于Unix和Linux的操作系统中的程序,可以让系统管理员给与sudoers文件中的用户有限的root权限。该漏洞是2011年7月的commit 8255ed69中引入的,距今已经快10年了,影响1.8.2到1.8.31p2版本,以及默认配置的1.9.0 到1.9.5p1稳定版本中。

Qualys安全研究人员独立验证了该漏洞,并开发出了Ubuntu 20.04 (Sudo 1.8.31)、Debian 10 (Sudo 1.8.27)和Fedora 33 (Sudo 1.9.2)版本的多个漏洞利用。

技术细节

如果Sudo以shell 模式执行来运行命令:

  • 可以通过-s 选项来设置Sudo的MODE_SHELL flag;或

  • 通过-s 选项来设置Sudo的MODE_SHELL 和 MODE_LOGIN_SHELL flag,然后在Sudo的 main()函数开始时,用parse_args() 覆写 argv (609-617行),具体方式是连接所有的命令行参数(587-595行)和用反斜杠(backslash)来编码(escape)所有的元字符(590-591行):

-------------------------------------------------------------------- 
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 != ''; 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_policy_main()中,set_cmnd()会连接所有的命令行参数到基于堆的缓存“user_args” (864-871行)中,解码所有的元字符(866-867行):

--------------------------------------------------------------  
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_args”缓存,“from”是递增的,并指向了空终止符后的第一个字符;

  • 865-869行的while 循环会读取和复制越界字符到“user_args”缓存。

也就是说,由于被复制到“user_args”缓存的越界字符并不在852-853行的size中,因此set_cmnd() 易受到基于堆的缓存溢出漏洞攻击。

但是,理论上没有命令行参数以单个反斜杠字符结束:如果在858行设置了MODE_SHELL或MODE_LOGIN_SHELL,也设置了MODE_SHELL(571行),那么parse_args() 会编码所有的元字符,包括反斜杠。

事实上,set_cmnd()中有漏洞的代码和parse_args()中的编码代码是有不同的条件的:

--------------------------------------------------------------------- 
819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { 
... 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { 
--------------------------------------------------------------------- 
versus:

--------------------------------------------------------------------- 
571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { 
---------------------------------------------------------------------

问题的关键是是否可以设置MODE_SHELL和MODE_EDIT 或 MODE_CHECK,而不是默认的MODE_RUN?
答案是No。如果设置了MODE_EDIT (-e opt ion)或MODE_CHECK (-l option),然后parse_args() 会从“valid_flags”移除MODE_SHELL,如果指定无效的flag就会因为错误退出:

--------------------------------------------------------------------- 
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); 
---------------------------------------------------------------------

但是,研究人员发现一个循环漏洞:如果以“sudoedit” 而非 “sudo” 执行Sudo,parse_args()就会自动设置MODE_EDIT (270行),而不重新设置“valid_flags”, “valid_flags”默认是含有MODE_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”,就可以设置MODE_EDIT 和 MODE_SHELL (而非 MODE_RUN),并通过以单反斜杠字符结尾的命令行参数来覆写基于堆的缓存“user_args”:

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

从攻击者的角度来看,该缓存溢出漏洞是非常理想的,主要原因有:


    1. 攻击者可以控制“user_args” 缓存的大小;


    1. 攻击者可以独立控制溢出的大小和内容;


    1. 攻击者可以将空字节写入覆写的缓存中。

比如,在amd64 Linux系统中,以下命令会分配一个24字节的“user_args”缓存,然后用“A=aB=b” (0x00623d4200613d41) 覆写下一个chunk的size域,用“C=cD=d” (0x00643d4400633d43) 覆写fd域,用“E=eF=f” (0x00663d4600653d45) 覆写bk域:

--------------------------------------------------------------------- 
env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\' 
--------------------------------------------------------------------- 

--|--------+--------+--------+--------|--------+--------+--------+--------+-- 
  |        |        |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.| 
--|--------+--------+--------+--------|--------+--------+--------+--------+-- 

              size  <---- user_args buffer ---->  size      fd       bk

PoC视频参见:https://player.vimeo.com/video/504872555

来源:https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit


本文作者:ang010ela

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/153115.html

Tags:
评论  (0)
快来写下你的想法吧!

ang010ela

文章数:56 积分: 711

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号