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:知柯信息安全