Qualys研究小组在sudo中发现了一个堆溢出漏洞,sudo是一个几乎无处不在的实用程序,可用于主要的类Unix操作系统。通过利用此漏洞,任何未经授权的用户都可以使用默认sudo配置在易受攻击的主机上获得root权限。
Sudo是一个强大的实用程序,它包含在大多数基于Unix和Linux的操作系统中。它允许用户以另一个用户的安全权限运行程序。近10年来,这个漏洞本身一直隐藏在人们的视线中。它于2011年7月引入(commit 8255ed69),在默认配置中影响从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)上获得完整的root权限。其他操作系统和发行版也可能被利用。
Qualys研究团队确认该漏洞后,Qualys立即进行了负责任的漏洞披露,并协调sudo的作者和开源发行版公布该漏洞。
技术细节
- 如果在“shell”模式下执行 Sudo 以运行命令(shell-c 命令)
- 通过-s选项,设置Sudo的MODE_SHELL标志
-
通过-i 选项设置 Sudo 的 MODE shell 和 MODE login shell 标志; 然后,在 Sudo 的 main ()的开头,parse args ()通过串联所有命令行参数(第587-595行)和用反斜杠转义所有元字符(第590-591行)来重写 argv
--------------------------------------------------------------------
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]”是参数的 null 结束符(即,不是空格字符) ;
- 在第867行,“ from”递增,并指向null 结束符;
- 在第868行,null 结束符被复制到“ user _ args”缓冲区,“ from”再次递增并指向 null 结束符之后的第一个字符(即超出参数的边界) ;
- 第865-869行的“ while”循环读取超出界限的字符并将其复制到“ user _ args”缓冲区。
换句话说,set cmnd ()容易受到基于堆的缓冲区溢出的影响,因为复制到“ user args”缓冲区的界外字符没有包含在其大小中(计算在 lines852-853)。
然而,理论上,任何命令行参数都不能以单个反斜杠字符结束: 如果设置了 MODE shell 或 MODE login shell (第858行,这是到达易受攻击代码的必要条件) ,那么设置了 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)) {
---------------------------------------------------------------------
对比:
---------------------------------------------------------------------
571 if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
---------------------------------------------------------------------
我们的问题是: 我们是否可以设置 MODE shell 和 MODE edit 或 MODE check (以达到易受攻击的代码) ,而不设置默认的 MODE run (以避免转义代码) ?
答案似乎是否定的: 如果我们设置 MODE edit (- e 选项,第361行)或 MODE check (- l 选项,第423和519行) ,然后 parse args ()从“ valid flags”(第363和424行)中删除 MODE shell,如果我们指定无效的标志如 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);
---------------------------------------------------------------------
但我们发现了一个漏洞:如果我们以 "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”缓冲区的大小(串联的命令行参数的大小,在第852-854行) ;
2)攻击者独立控制溢出本身的大小和内容(我们最后的命令行参数后面跟着我们的第一个环境变量,这些变量不包括在第852-853行的大小计算中) ;
3)攻击者甚至可以向溢出的缓冲区写入空字节(每个命令行参数或者以一个反斜杠结尾的环境变量向“ user _ args”写入一个空字节,第866-868行)。
例如,在 amd64 Linux 上,下面的命令分配一个24字节的“ user _ args”缓冲区(一个32字节的堆块) ,并用“ a = a 0B = b0”(0x00623d4200613d41)覆盖下一个块的大小字段,用“ c = c 0D = d 0”(0x00643d4400633d43)覆盖其 fd 字段及其 bk 字段“ e = e0f = f0”(0x00663d4600653d45) :
---------------------------------------------------------------------
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
解决方案
- 鉴于该漏洞的攻击面很广,Qualys建议用户立即应用该漏洞的补丁。
- Qualys客户可以在漏洞知识库中搜索CVE-2021-3156,以确定所有易受此漏洞影响的QID和资产。
- 如果您不是客户,请开始您的免费Qualys VMDR试用版,以获得对CVE-2021-3156的QIDs(检测)的完整访问权限,这样您就可以识别您的易受攻击的资产。
Qualys 覆盖范围
QID 374891: Sudo Heap-based Buffer Overflow 漏洞。
该QID在vulnsigs版本VULNSIGS-2.5.90-4和Linux Cloud Agent manififest版本lx_manifest-2.5.90.4-3中可用。
见问题解答(FAQs)
哪些版本容易受到攻击?
以下版本的 sudo 易受攻击:
- 从1.8.2到1.8.31p2的所有旧版本
- 从1.9.0到1.9.5p1的所有稳定版本
如何测试我是否有易受攻击的版本?
- 要测试系统是否易受攻击,请以非 root 用户登录系统。
- 运行命令“sudoedit -s /”
- 如果系统易受攻击,它将响应以“sudoedit:”开头的错误
- 如果系统打了补丁,它将以一个以“usage:”开头的错误响应
1.8.2之前的版本容易受到攻击吗?
没有。见上文解释。
是否需要一个本地用户来利用这个漏洞?
是的,但是这个用户不需要是特权用户或成为sudoers列表的一部分。例如,即使是账户 "nobody "也可以利用这个问题。
为什么把这个漏洞命名为 “Baron Samedit”?
这是对Baron Samedi和sudoedit的戏称。
Qualys 研究团队是否会发布此漏洞的利用代码?
不会。
参考资料:
https://bbs.pediy.com/thread-265672.htm
https://seclists.org/fulldisclosure/2021/Jan/79