CVE-2021-3156(sudo Heap overflow sudo本地提權復現與分析)

CVE-2021-3156: Heap-Based Buffer Overflow in Sudo (Baron Samedit)
CVE-2021-3156簡介:
sudo是一個幾乎無處不在的實用程序,可用於主要的類Unix操作系統。通過利用此漏洞,任何未經授權的用戶都可以使用默認sudo配置在易受攻擊的主機上獲得root權限。
Sudo是一個強大的實用程序,它包含在大多數基於Unix和Linux的操作系統中。它允許用戶以另一個用戶的安全權限運行程序。近10年來,這個漏洞本身一直隱藏在人們的視線中。
根據安全研究員[email protected]的報告:
受到威脅的系統有:
Ubuntu 18.04.5 (Bionic Beaver) - sudo 1.8.21, libc-2.27
Ubuntu 20.04.1 (Focal Fossa) - sudo 1.8.31, libc-2.31
Debian 10.0 (Buster) - sudo 1.8.27, libc-2.28


通過審計Sudo Mode模塊代碼:






571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
   
    
572         char **av, *cmnd = NULL; 
573         int ac = 1; 
... 
581             cmnd = dst = reallocarray(NULL, cmnd_size, 2); 
... 
587             for (av = argv; *av != NULL; av++) {
   
    
588                 for (src = *av; *src != '\0'; src++) {
   
    
589                     /* quote potential meta characters */ 
590                     if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$') 
591                         *dst++ = '\\'; 
592                     *dst++ = *src; 
593                 } 
594                 *dst++ = ' '; 
595             } 
... 
600             ac += 2; /* -c cmnd */ 
... 
603         av = reallocarray(NULL, ac + 1, sizeof(char *)); 
... 
609         av[0] = (char *)user_details.shell; /* plugin may override shell */ 
610         if (cmnd != NULL) {
   
    
611             av[1] = "-c"; 
612             av[2] = cmnd; 
613         } 
614         av[ac] = NULL; 
615  
616         argv = av; 
617         argc = ac; 
618     } 

稍後,在sudoers\u policy\u main()中,set\cmnd()將命令行參數連接到基於堆的緩衝區“user\u args”(第864-871行)中,並取消對元字符(第866-867行)的scape,“用於sudoers匹配和日誌記錄目的”:

819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
   
    
... 
852             for (size = 0, av = NewArgv + 1; *av; av++) 
853                 size += strlen(*av) + 1; 
854             if (size == 0 || (user_args = malloc(size)) == NULL) {
   
    
... 
857             } 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
   
    
... 
864                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
   
    
865                     while (*from) {
   
    
866                         if (from[0] == '\\' && !isspace((unsigned char)from[1])) 
867                             from++; 
868                         *to++ = *from++; 
869                     } 
870                     *to++ = ' '; 
871                 } 
... 
884             } 
... 
886     } 

在第866行,“from[0]”是反斜槓字符,“from[1]”是參數的空終止符(即,不是空格字符);
在第867行,“from”遞增並指向空終止符;
在第868行,空終止符被複制到“user\u args”緩衝區,“from”再次遞增並指向空終止符後面的第一個字符(即,超出參數的邊界);
第865-869行的“while”循環讀取越界字符並將其複製到“user\u args”緩衝區。
換句話說,set\u cmnd()易受基於堆的緩衝區溢出的攻擊,因爲複製到“user\u args”緩衝區的越界字符未包含在其大小中(在第852-853行計算)。



但是,理論上,沒有命令行參數可以以單個反斜槓字符結尾:如果設置了MODE\u SHELL或MODE\u LOGIN\u SHELL(第858行,這是到達易受攻擊代碼的必要條件),則設置MODE\u SHELL(第571行),parse\u args()已經轉義了所有元字符,包括反斜槓(即。,它用第二個反斜槓逃過了每一個反斜槓)。
但實際上,set\u cmnd()中的易受攻擊代碼和
parse\u args()中的轉義代碼被稍微不同的條件包圍:

819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
   
    
... 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
   
    
571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
   
    

我們的問題是:我們是否可以設置模式\u SHELL和模式\u EDIT或模式\u CHECK(以訪問易受攻擊的代碼),而不是默認模式\u RUN(以避免轉義代碼)?

答案似乎是否定的:如果我們設置MODE_EDIT(-e option,第361行)或MODE_CHECK(-l option,第423行和第519行),那麼parse_args()將MODE_SHELL從“valid_flags”(第363行和第424行)中刪除,如果我們指定了MODE_SHELL(第532-533行)之類的無效標誌,則返回錯誤:

358                 case 'e': 
... 
361                     mode = MODE_EDIT; 
362                     sudo_settings[ARG_SUDOEDIT].value = "true"; 
363                     valid_flags = MODE_NONINTERACTIVE; 
364                     break; 
... 
416                 case 'l': 
... 
423                     mode = MODE_LIST; 
424                     valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST; 
425                     break; 
... 
518     if (argc > 0 && mode == MODE_LIST) 
519         mode = MODE_CHECK; 
... 
532     if ((flags & valid_flags) != flags) 
533         usage(1); 

但是我們發現了一個漏洞:如果我們將Sudo作爲“sudoedit”而不是“Sudo”執行,那麼parse\u args()會自動設置模式\u EDIT(第270行),但不會重置“valid\u flags”,“valid\u flags”默認包括模式\u SHELL(第127行和第249行):

127 #define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL) 
... 
249     int valid_flags = DEFAULT_VALID_FLAGS; 
... 
267     proglen = strlen(progname); 
268     if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
   
    
269         progname = "sudoedit"; 
270         mode = MODE_EDIT; 
271         sudo_settings[ARG_SUDOEDIT].value = "true"; 
272     } 

因此,如果我們執行“sudoedit-s”,那麼我們同時設置模式\u EDIT和模式\u SHELL(而不是模式\u RUN),我們就避免了轉義代碼,到達易受攻擊的代碼,並通過以單個反斜槓字符結尾的命令行參數使基於堆的緩衝區“user \u args”溢出:

sudoedit-s'\'`perl-e'print“A”x 65536'
malloc(): corrupted top size 
Aborted (core dumped) 

從攻擊者的角度來看,這種緩衝區溢出非常理想,原因如下:
1) 攻擊者控制可以溢出的“user\u args”緩衝區的大小(在第852-854行連接的命令行參數的大小);

2) 攻擊者獨立控制溢出本身的大小和內容(最後一個命令行參數後面緊跟着第一個環境變量,這些變量不包括在第852-853行的大小計算中);

3) 攻擊者甚至可以將空字節寫入溢出的緩衝區(每個以單個反斜槓結尾的命令行參數或環境變量都會將空字節寫入第866-868行的“用戶參數”)。

例如,在amd64 Linux上,下面的命令分配一個24字節的“user\u args”緩衝區(一個32字節的堆塊)並用。
歡迎關注CSDN:知柯信息安全

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