1. 程式人生 > 其它 >CVE-2021-3156:Sudo中基於堆的緩衝區溢位 (Baron Samedit)

CVE-2021-3156:Sudo中基於堆的緩衝區溢位 (Baron Samedit)

技術標籤:Penetration testsudoCVE緩衝區溢位Baron Samedit

Qualys研究小組在sudo中發現了一個堆溢位漏洞,sudo是一個幾乎無處不在的實用程式,可用於主要的類Unix作業系統。通過利用此漏洞,任何未經授權的使用者都可以使用預設sudo配置在易受攻擊的主機上獲得root許可權。

Sudo是一個強大的實用程式,它包含在大多數基於Unix和Linux的作業系統中。它允許使用者以另一個使用者的安全許可權執行程式。近10年來,這個漏洞本身一直隱藏在人們的視線中。它於2011年7月引入(commit 8255ed69),在預設配置中影響從1.8.2到1.8.31p2的所有舊版本和從1.9.0到1.9.5p1的所有穩定版本。

成功利用此漏洞可使任何未經授權的使用者在易受攻擊的主機上獲得根使用者許可權。Qualys安全研究人員已經能夠獨立驗證該漏洞,開發多種漏洞變體,並在Ubuntu 20.04(Sudo 1.8.31)、Debian 10(Sudo 1.8.27)和Fedora 33(Sudo 1.9.2)上獲得完整的root許可權。其他作業系統和發行版也可能被利用。

Qualys研究團隊確認該漏洞後,Qualys立即進行了負責任的漏洞披露,並協調sudo的作者和開源發行版公佈該漏洞。

技術細節

  • 如果在“shell”模式下執行 Sudo 以執行命令(shell-c 命令)
  • 通過-s選項,設定Sudo的MODE_SHELL標誌
  • 通過-i 選項設定 Sudo 的 MODEshell 和 MODEloginshell 標誌; 然後,在 Sudo 的 main ()的開頭,parseargs ()通過串聯所有命令列引數(第587-595行)和用反斜槓轉義所有元字元(第590-591行)來重寫 argv

-------------------------------------------------------------------- 
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]”是引數的 null 結束符(即,不是空格字元) ;
  • 在第867行,“ from”遞增,並指向null 結束符;
  • 在第868行,null 結束符被複制到“ user _ args”緩衝區,“ from”再次遞增並指向 null 結束符之後的第一個字元(即超出引數的邊界) ;
  • 第865-869行的“ while”迴圈讀取超出界限的字元並將其複製到“ user _ args”緩衝區。

換句話說,setcmnd ()容易受到基於堆的緩衝區溢位的影響,因為複製到“ userargs”緩衝區的界外字元沒有包含在其大小中(計算在 lines852-853)。

然而,理論上,任何命令列引數都不能以單個反斜槓字元結束: 如果設定了 MODEshell 或 MODEloginshell (第858行,這是到達易受攻擊程式碼的必要條件) ,那麼設定了 MODEshell (第571行) ,並且 parse _ args ()已經轉義了所有元字元,包括反斜槓(即,它用第二個反斜槓對每個反斜槓進行轉義)。
但實際上,set_cmnd()中的弱勢程式碼和parse_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)) { 
---------------------------------------------------------------------

我們的問題是: 我們是否可以設定 MODEshell 和 MODEedit 或 MODEcheck (以達到易受攻擊的程式碼) ,而不設定預設的 MODErun (以避免轉義程式碼) ?

答案似乎是否定的: 如果我們設定 MODEedit (- e 選項,第361行)或 MODEcheck (- l 選項,第423和519行) ,然後 parseargs ()從“ validflags”(第363和424行)中刪除 MODEshell,如果我們指定無效的標誌如 MODEshell (第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); 
---------------------------------------------------------------------

但我們發現了一個漏洞:如果我們以 "sudoedit "而不是 "sudo "的方式執行Sudo,那麼parse_args()會自動設定MODE_EDIT(第270行),但不會重置 "valid_flags",而且 "valid_flags "預設包括MODE_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”,然後我們同時設定 MODEedit 和 MODEshell (但不設定 MODErun) ,我們就避免了轉義程式碼,達到了易受攻擊的程式碼,並且通過一個以一個反斜槓字元結尾的命令列引數溢位了基於堆的緩衝區“ userargs”:

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

從攻擊者的角度來看,這種緩衝區溢位是理想的,原因如下:

1)攻擊者控制可以溢位的“ user _ args”緩衝區的大小(串聯的命令列引數的大小,在第852-854行) ;

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

3)攻擊者甚至可以向溢位的緩衝區寫入空位元組(每個命令列引數或者以一個反斜槓結尾的環境變數向“ user _ args”寫入一個空位元組,第866-868行)。

例如,在 amd64 Linux 上,下面的命令分配一個24位元組的“ user _ args”緩衝區(一個32位元組的堆塊) ,並用“ a = a 0B = b0”(0x00623d4200613d41)覆蓋下一個塊的大小欄位,用“ c = c 0D = d 0”(0x00643d4400633d43)覆蓋其 fd 欄位及其 bk 欄位“ e = e0f = f0”(0x00663d4600653d45) :

--------------------------------------------------------------------- 
env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\' 
--------------------------------------------------------------------- 
 
--|--------+--------+--------+--------|--------+--------+--------+--------+-- 
  |        |        |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.| 
--|--------+--------+--------+--------|--------+--------+--------+--------+-- 
 
              size  <---- user_args buffer ---->  size      fd       bk

解決方案

  • 鑑於該漏洞的攻擊面很廣,Qualys建議使用者立即應用該漏洞的補丁。
  • Qualys客戶可以在漏洞知識庫中搜索CVE-2021-3156,以確定所有易受此漏洞影響的QID和資產。
  • 如果您不是客戶,請開始您的免費Qualys VMDR試用版,以獲得對CVE-2021-3156的QIDs(檢測)的完整訪問許可權,這樣您就可以識別您的易受攻擊的資產。

Qualys 覆蓋範圍

QID 374891: Sudo Heap-based Buffer Overflow 漏洞。

該QID在vulnsigs版本VULNSIGS-2.5.90-4和Linux Cloud Agent manififest版本lx_manifest-2.5.90.4-3中可用。

見問題解答(FAQs)

哪些版本容易受到攻擊?

以下版本的 sudo 易受攻擊:

  • 從1.8.2到1.8.31p2的所有舊版本
  • 從1.9.0到1.9.5p1的所有穩定版本

如何測試我是否有易受攻擊的版本?

  • 要測試系統是否易受攻擊,請以非 root 使用者登入系統。
  • 執行命令“sudoedit -s /”
  • 如果系統易受攻擊,它將響應以“sudoedit:”開頭的錯誤
  • 如果系統打了補丁,它將以一個以“usage:”開頭的錯誤響應

1.8.2之前的版本容易受到攻擊嗎?

沒有。見上文解釋。
是否需要一個本地使用者來利用這個漏洞?
是的,但是這個使用者不需要是特權使用者或成為sudoers列表的一部分。例如,即使是賬戶 "nobody "也可以利用這個問題。
為什麼把這個漏洞命名為 “Baron Samedit”?
這是對Baron Samedi和sudoedit的戲稱。
Qualys 研究團隊是否會發布此漏洞的利用程式碼?
不會。

參考資料:

https://bbs.pediy.com/thread-265672.htm

https://seclists.org/fulldisclosure/2021/Jan/79