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 的 MODE shell 和 MODE login shell 标志; 然後,在 Sudo 的 main ()的開頭,parse args ()通過串聯所有指令行參數(第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”緩沖區。
換句話說,set cmnd ()容易受到基于堆的緩沖區溢出的影響,因為複制到“ user args”緩沖區的界外字元沒有包含在其大小中(計算在 lines852-853)。
然而,理論上,任何指令行參數都不能以單個反斜杠字元結束: 如果設定了 MODE shell 或 MODE login shell (第858行,這是到達易受攻擊代碼的必要條件) ,那麼設定了 MODE shell (第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)) {
---------------------------------------------------------------------
我們的問題是: 我們是否可以設定 MODE shell 和 MODE edit 或 MODE check (以達到易受攻擊的代碼) ,而不設定預設的 MODE run (以避免轉義代碼) ?
答案似乎是否定的: 如果我們設定 MODE edit (- e 選項,第361行)或 MODE check (- l 選項,第423和519行) ,然後 parse args ()從“ valid flags”(第363和424行)中删除 MODE shell,如果我們指定無效的标志如 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);
---------------------------------------------------------------------
但我們發現了一個漏洞:如果我們以 "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”,然後我們同時設定 MODE edit 和 MODE shell (但不設定 MODE run) ,我們就避免了轉義代碼,達到了易受攻擊的代碼,并且通過一個以一個反斜杠字元結尾的指令行參數溢出了基于堆的緩沖區“ user args”:
---------------------------------------------------------------------
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