查看原文
其他

CVE-2021-3156:隐藏10年之久的 Sudo 漏洞,可使任意用户获得root 权限(详述)

Qualys 代码卫士 2022-05-23

 聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士团队






Qualys 公司的研究团队在几乎所有主流 Unix 类操作系统都部署的 sudo 中发现了一个隐藏近10年之久的堆溢出漏洞,可导致任意低权限用户在使用默认 sudo 配置的易受攻击主机上获得 root 权限。该漏洞被命名为 “Baron Samedit”,是研究员对 Baron Samedi 和 sudoedit 的戏称。




Sudo 是一款几乎包含在所有基于 Unix 和 Linux 操作系统中的强大工具,可使用户以其它用户的安全权限运行程序。该漏洞本身隐藏了近10年之久。它源自2011年7月 (commit 8255ed69) 。这个漏洞易遭利用,用户无需是权限用户或者位于 sudoers 列表中。例如,甚至是 “nobody” 账户也可利用该漏洞。






影响版本




默认配置下:

  • 自1.8.2到1.8.31p2 的所有遗留版

  • 从 1.9.0 至1.9.5p1的所有稳定版

成功利用该漏洞可导致任意非权限用户获得易受攻击主机上的 root 权限。研究人员独立验证了该漏洞并开发出多个 exploit 变体,并获得在 Ubuntu 20.04 (Sudo 1.8.31)、Debian 10 (Sudo 1.8.27) 和 Fedora 33 (Sudo 1.9.2) 的完整 root 权限。其它操作系统和发布版本也可能遭利用。

研究人员证实该漏洞后,负责任地披露了该漏洞并和 Sudo 的作者及开源发行版本联系,公开了该漏洞。






披露时间线




  • 2021-01-13:向 Todd.Miller@sudo 发布安全通告

  • 2021-01-19:向 distros@openwall 发布安全公告和补丁

  • 2021-01-26:协同发布





PoC 视频








技术详情




如执行 Sudo, 在 “shell” 模式下运行命令 (shell –c 命令):

  • 或者通过 –s 选项,设置 Sudo 的 MODE_SHELL 标记;或者

  • 通过 –I 选项,设置 Sudo 的 MODE_SHELL 和 MODE_LOGIN_SHELL 标记;之后再 Sudo 的 main() 开头,parse_args() 重写 argv(第609行至617行),方法是拼接所有的命令行参数(第587行至595行)并通过反斜杠转义所有的元字符:

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_policy_main() 中,set_cmnd() 将命令行参数拼接到堆缓冲区 “user_args”(第864行到871行)并反转义元字符(第866行至867行),“实现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” 缓冲区的界外字符并未包含在其大小中(在第852到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(-I 选项,第423行和519行),则 parse_args() 从 “valid_flags” 删除 MODE_SHELL(第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);


但我们发现一个问题:如果我们以 “sudoedit” 而非 “sudo” 执行 Sudo,则 parse_args() 自动设置 MODE_EDIT(第270行)但并未重置 “valid_flags”,而 “valid_flags” 默认包括 MODE_SHELL(第127行和249行):

_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(而非 MDOE_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行至953行的大小中);

(3)攻击者甚至能够将空字节写入溢出的缓冲区中(以单个反斜杠结尾的每个命令行参数或环境变量将空字节写入 “user_args” 中,见第866行至868行)。

例如,在 amd64 Linux 上,如下命令分配一个24字节大小的 “user_args” 缓冲区(一个32字节大小的堆块)并通过 “A=A\0B=B\0” 覆写下一个块的size字段 (0x00623d4200613d41),以 “C=c\0D=d\0” (0x00643d4400633d43) 覆写 fd 字段,并以 E=e\0F=f\0” (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






解决方案




鉴于该漏洞的攻击面广泛,建议用户立即打补丁。

检测版本是否受影响:

检测系统是否受影响,以非 root 用户身份登录系统,运行命令 “sudoedit -s /”:

  • 如系统易受攻击,则会响应一个以“sudoedit” 开头的错误信息

  • 如系统已修复,则会响应一个以 “usage” 开头的错误信息




推荐阅读
Sudo bug 可导致非权限 Linux 和 MacOS 用户以根身份运行命令
Linux Sudo 被曝漏洞,可导致用户以 root 权限运行命令
Linux存在缺陷 Sudo用户可获根权限



原文链接

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


题图:Pixabay License


本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。



奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 或 "” 吧~



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存