Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2021-3156-Heap-Based-Buffer-Overflow-in-Sudo-Baron-Samedit- — 针对CVE-2021-3156(Sudo中的堆缓冲区溢出)的技术分析和漏洞利用开发,包括三个在主要Linux发行版上实现本地权限提升的有效漏洞利用。 | Kitploit
工具/GitHubGitHub/sornphut/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit-
权限提升漏洞分析漏洞利用渗透测试论文与研究学习与教育二进制利用
GitHubsornphut/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit-

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2021-3156-Heap-Based-Buffer-Overflow-in-Sudo-Baron-Samedit-

针对CVE-2021-3156(Sudo中的堆缓冲区溢出)的技术分析和漏洞利用开发,包括三个在主要Linux发行版上实现本地权限提升的有效漏洞利用。

查看仓库
231年前尚未审核

Qualys Security Advisory

Baron Samedit:Sudo 中的堆缓冲区溢出 (CVE-2021-3156)

======================================================================== 目录

概述 分析 利用 致谢 时间线

======================================================================== 概述

我们在 Sudo (https://www.sudo.ws/) 中发现了一个堆缓冲区溢出漏洞。该漏洞:

  • 可由任何本地用户(普通用户和系统用户,sudoers 和非 sudoers)利用,无需认证(即攻击者无需知道用户密码);

  • 于 2011 年 7 月引入(提交 8255ed69),影响 1.8.2 至 1.8.31p2 的所有旧版本以及 1.9.0 至 1.9.5p1 的所有稳定版本,均处于默认配置。

我们为这个漏洞开发了三种不同的利用方式,并在 Ubuntu 20.04 (Sudo 1.8.31)、Debian 10 (Sudo 1.8.27) 和 Fedora 33 (Sudo 1.9.2) 上获得了完整的 root 权限。其他操作系统和发行版也可能被利用。

======================================================================== 分析

如果 Sudo 以 "shell" 模式执行命令(shell -c command):

  • 通过 -s 选项(设置 Sudo 的 MODE_SHELL 标志);

  • 或通过 -i 选项(设置 Sudo 的 MODE_SHELL 和 MODE_LOGIN_SHELL 标志);

那么,在 Sudo 的 main() 函数开头,parse_args() 会重写 argv(第 609-617 行),将命令行参数连接起来(第 587-595 行),并用反斜杠转义所有元字符(第 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 != '\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(-l 选项,第 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 行):


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)

从攻击者的角度来看,这个缓冲区溢出非常理想:

  • 我们可以控制被溢出 "user_args" 缓冲区的大小(第 852-854 行中连接的命令行参数的大小);

  • 我们可以独立控制溢出本身的大小和内容(最后一个命令行参数之后紧跟着第一个环境变量,这些变量未包含在第 852-853 行的大小计算中);

  • 我们甚至可以向被溢出的缓冲区写入 null 字节(每个以单个反斜杠结尾的命令行参数或环境变量都会向 "user_args" 写入一个 null 字节,第 866-868 行)。

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

======================================================================== 利用

由于 Sudo 在其 main() 函数的最开始调用了本地化函数:


154 setlocale(LC_ALL, ""); 155 bindtextdomain(PACKAGE_NAME, LOCALEDIR); 156 textdomain(PACKAGE_NAME);

并将翻译字符串(通过 gettext() 函数和 _() 宏)传递给格式字符串函数,例如:

下载工具