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行,处理命令行参数
1 | for (n = 1; n < (guint) argc; n++) // 注意这一句,如果我们传递了参数后,n应该在结束循环时与argc相等,如果没有参数,argc就为0,但是由于此处n的初始值为1,因此如果没有参数被传递,1就变成了argc(0)+1,如果后续继续使用n的话,就有可能出现问题 |
然后在610行,获取PROGRAM参数名称,也就是需要执行的程序
1 | path = g_strdup (argv[n]); // 分析代码,我们可以发现n在此时被使用,g_strdup复制目标字符串,但是如果我们不传递任何参数,g_strdup用于拷贝字符串,如果没有参数传递,这里就产生内存越界读取了 |
整理一下,得出,在不传递任何参数时,情况如下
- 在第 534 行,整数 n 的设置为 1
- 在第 610 行,从 argv[1] 越界读取指针路径
- 在第 639 行,指针 s 被越界写入 argv[1]
现在很重要的一点就是,我们想要知道,当越界的argv[1]
包含了什么内容
当我们使用execve()
执行一个程序时,内核会将我们的参数、环境字符串以及指针(argv 和 envp)复制到新程序栈的末尾;如下所示:
1 | |---------+---------+-----+------------|---------+---------+-----+------------| |
也就是说,被越界访问的实际上是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完全清除了环境变量,因此可以利用的选项比较少
1 | if (clearenv () != 0) |
可以发现代码中多处调用了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
1 |
|
1 | gcc -shared -fPIC payload.c -o payload.so |
构造exploit
LC_MESSAGES
用来指定要转换的字符集XAUTHORITY
设置为非法值以跳过pkexec的正常执行,我们只需要触发日志函数来实现提权
1 |
|
1 | 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 权限提升漏洞分析
https://cn.4xpl0r3r.com/漏洞分析/CVE-2021-4034-Linux-Polkit-权限提升漏洞分析/