CVE-2021-4034 pkexec本地提权漏洞
前言
Qualys 研究团队在 polkit 的 pkexec 中发现了一个内存损坏漏洞,该 SUID 根程序默认安装在每个主要的 Linux 发行版上。这个易于利用的漏洞允许任何非特权用户通过在其默认配置中利用此漏洞来获得易受攻击主机上的完全 root 权限。
pkexec
Polkit(以前称为 PolicyKit)是一个用于在类 Unix 操作系统中控制系统范围权限的组件。它为非特权进程与特权进程通信提供了一种有组织的方式。也可以使用 polkit 执行具有提升权限的命令,使用命令 pkexec 后跟要执行的命令(具有 root 权限)。
运行

完成认证即可使用root权限执行文件

漏洞复现

漏洞分析
变量n初始值被设置为1,循环执行的次数为参数的个数,但是若参数个数为0时,此时变量n仍然为1,并且后面执行的语句会将argv[n]的值取出则造成了数组越界。

test1.c
1 | |
将test.c编译执行,发现argv[0]为执行文件所在路径。并且参数个数也是为1,不会出现为0的情况。因此在这种情况下pkexec不会出现数组越界的情况。

test2.c
1 | |
将test2.c编译执行,使用execve函数启动test1文件,发现此时的argc为0。

那么使用execve函数调用pkexec文件就有可能会出现argc为0,造成数组越界,下图为调试pkexec时的情况,此时的argc为0,但是n为1,通过源码可以看到后续会读取argv[n]的值,因此造成了数组越界。

漏洞利用
pkexec文件会执行validate_environment_variable (key, value)用于检测key所对应的环境变量是否合法。

若key所对应的环境变量不合法则会采用g_printerr函数打印信息,log_message函数内部也是调用了g_printerr进行信息的打印。

exp利用g_printerr打印错误信息时特殊的执行流程进行getshell。
当Linux中CHARSET不是设置为UTF-8格式,则会调用iconv,用于将文本从一种编码转化为另一种编码。
在调用iconv之前需要通过使用iconv_open分配转化描述符号。
iconv_open函数受到GCONV_PATH环境变量影响
- 若
GCONV_PATH未设置,那么iconv_open会加载系统默认的模块配置的缓存文件。- 默认的配置文件位于
/usr/lib/gconv/gconv-modules
- 默认的配置文件位于
- 若
GCONV_PATH被设置,则会优先加载设置路径下的配置文件。
查看默认的配置文件信息gconv-modules,该配置文件指定了编码转换的键值对,并且通过指定的so文件执行转换。

main_g_printerr.c文件中调用了g_printerr函数,而test3.c则是我们稍后需要编译成.so的文件,尝试利用g_printerr函数执行自行编译的so库。
1 | |
将main_g_printerr.c编译为main_g_printerr,test3.c编译为test3.so,gconv-modules为配置文件,内容如上。

设置CHARSET为ABCD,因为配置文件写的是从ABCD转化为UTF-8,然后将环境变量GCONV_PATH设置为当前目录。执行main_g_printerr发现输出的是.so文件中的Hello test3

pkexec是具有suid特殊权限的文件,因此执行pkexec文件时是具有root权限的。

linux的动态链接器会在特权程序执行的时候清楚危险的环境变量,因此使用execve启动pkexec时,即使设置了GCONV_PATH也会被连接器清除。如下图所示test4具有suid权限,在test5中使用execve启动test4,并且设置了GCONV_PATH环境变量,但是可以看到test4的环境变量中并没有GCONV_PATH

因此需要使用pkexec中存在的数组越界漏洞,将GCONV_PATH写入
变量argv与变量envp在内存中是连续的,如下图所示,图片来自于PwnKit: Local Privilege Escalation Vulnerability Discovered in polkit’s pkexec (CVE-2021-4034)

当我们使用execve启动pkexec时,argv是NULL,因此argc的值为0,但是pkexec会默认将argc的值赋值为1,因此argv[argc] = argv[1] = envp[0],因此envp中的值会被越界读取。
利用数组越界写入GCONV_PATH=.
使用execve启动pkexec时envp[0]的值为FileName:.,并在当前目录下新建名为GCONV_PATH=.的文件夹,该文件夹下新建名为FileName:.的文件。
首先pkexec会取出argv[1](即envp[0])的值,接着通过g_find_program_in_path函数获取文件路径从而构造出GCONV_PATH=.FileName:.,接着该值会重新覆盖envp[0],至此GCONV_PATH被成功写入。

GDB调试
数组越界读

环境变量构造

成功引入环境变量

最后就是使得pkexec使用g_printerr打印错误信息即可。
参考文章
https://bbs.pediy.com/thread-271423.htm
https://github.com/berdav/CVE-2021-4034