赞
踩
本篇是SUID提权系列文章的第三篇。建议大家按顺序先阅读前两篇铺垫。
第一篇我们讲解了SUID提权的基础知识,linux系统的用户、文件权限与进程凭证。
第二篇我们讲解了SUID提权的原理,只要让具有SUID-root权限的程序加载我们的提权so,我们就赢了。
第三篇我们来讲解CVE-2021-4034漏洞的原理以及利用过程。本文会按照下面主题进行分享:
CVE的英文全称是Common Vulnerabilities & Exposures(公共漏洞和暴露)。
官网:
https://cve.mitre.org/。
CVE-2021-4034是一个SUID提权漏洞,利用具有SUID-root权限的pkexec,精心构造参数及运行环境,使其加载我们准备好提权的so。
CVE-2021-4034:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-4034
看看如何让pkexec加载任意so。我们需要准备一份pkexec源码。
pkexec源码:
https://www.freedesktop.org/software/polkit/releases/polkit-0.120.tar.gz
查看src/programs/pkexec.c文件。部分代码如下:
435 main (int argc, char *argv[]) 436 { ... 534 for (n = 1; n < (guint) argc; n++) 535 { ... 568 } ... 610 path = g_strdup (argv[n]); ... 629 if (path[0] != '/') 630 { ... 632 s = g_find_program_in_path (path); ... 639 argv[n] = path = s; 640 }
我们来分析一下这一段代码,
534-568行:main函数处理命令行参数。
610-640行:pkexec在 PATH 环境变量的目录中搜索要执行的程序,如果其路径不是绝对路径,argv[n] = path = s。
不幸的是,如果命令行参数argc的数量为0,那么:
但是从这个越界的argv[1]中读取和写入的到底是什么?要回答这个问题,我们必须简短地离题。我们需要了解execve函数,函数原型如下:
int execve(const char *filename, char *const argv[], char *const envp[]);
当我们使用execve函数启动一个新程序时,内核将我们的参数、环境变量字符串和指针(argv和envp)复制到新程序堆栈的末尾;
例如execve("/usr/bin/pkexec",{“program”,"-option",…},{“value”,“PATH=name”,…})的内存布局如下:
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
"program" "-option" NULL "value" "PATH=name" NULL
显然,因为argv和envp指针在内存中是连续的,如果argc为0,那么越界argv[1]实际上是envp[0],指向我们的第一个环境变量“value”的指针。所以:
下面两段话,值得多读几篇,本漏洞的核心:
如果我们的PATH环境变量是“PATH=name”,并且如果目录“name”存在(在当前工作目录中),并且包含一个名为“value”的可执行文件,那么指向字符串“name/value”的指针将被越界写入envp[0];
如果我们的PATH是“PATH=name=.”,并且如果目录“name=.” 存在并包含一个名为“value”的可执行文件,那么指向字符串“name=./value”的指针将被越界写入envp[0]。
利用代码:
https://github.com/berdav/CVE-2021-4034
第639行越界写入后不久,在第702行将完全清除其环境变量。我们需要精心选择一个环境变量进行利用,这里就考验大家的知识广度了,此漏洞很早就被发现,但是成功利用是在最近。
639 argv[n] = path = s;
...
657 for (n = 0; environment_variables_to_save[n] != NULL; n++)
658 {
659 const gchar *key = environment_variables_to_save[n];
...
662 value = g_getenv (key);
...
670 if (!validate_environment_variable (key, value))
...
675 }
...
702 if (clearenv () != 0)
为了将错误消息打印到stderr,pkexec调用g_printerr函数
88 log_message (gint level, 89 gboolean print_to_stderr, 90 const gchar *format, 91 ...) 92 { ... 125 if (print_to_stderr) 126 g_printerr ("%s\n", s); ... 383 validate_environment_variable (const gchar *key, 384 const gchar *value) 385 { ... 406 log_message (LOG_CRIT, TRUE, 407 "The value for the SHELL variable was not found the /etc/shells file"); 408 g_printerr ("\n" 409 "This incident has been reported.\n");
g_printerr函数通常打印UTF-8错误消息,但如果环境变量CHARSET不是UTF-8,它可以在另一个charset中打印消息(注意:CHARSET不是安全敏感的,它不是“不安全”的环境变量)。要将消息从UTF-8转换为另一个字符集,g_printerr函数调用了glibc的函数iconv_open。
要将消息从一个字符集转换为另一个字符集,iconv_open函数执行了小型共享库;通常,这些三胞胎(源字符集、目的字符集和库名称)从默认配置文件/usr/lib/gconv/gconv-modules中读取。环境变量GCONV_PATH可以强制iconv_open函数读取另一个配置文件;当然,GCONV_PATH是“不安全”的环境变量之一(因为它会导致执行任意库),但被ld.so从SUID程序的环境变量中移除。
不幸的是,CVE-2021-4034允许我们将GCONV_PATH重新引入pkexec的环境,并以root用户身份执行我们自己的共享库。
去掉pkexec的SUID-root权限就好了
chmod 0755 /usr/bin/pkexec
欢迎大家使用常用聊天软件关注、点赞、评论交流~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。