背景介紹
2021 年 1 月 26 日,Qualys Research Labs在 sudo 發(fā)現(xiàn)了一個(gè)缺陷。sudo 解析命令行參數(shù)的方式時(shí),錯(cuò)誤的判斷了截?cái)喾?,從而?dǎo)致攻擊者可以惡意構(gòu)造載荷,使得sudo發(fā)生堆溢出,該漏洞在配合環(huán)境變量等分配堆以及釋放堆的原語(yǔ)下,可以致使本地提權(quán)。
環(huán)境搭建
環(huán)境版本
? ubuntu 20.04
? sudo-1.8.31p2
采用下述命令進(jìn)行編譯安裝
cd ./sudo-SUDO_1_8_31p2
mkdir build
./configure --prefix=/home/pwn/sudo CFLAGS=”-O0 -g"
make && make install
漏洞驗(yàn)證
#poc
./sudoedit -s '\' 11111111111111111111111111111111111111111111111111111111111111111111
執(zhí)行上述POC執(zhí)行sudoedit會(huì)出現(xiàn)malloc():invalid size的字樣,這是典型的堆溢出后導(dǎo)致的異常。
漏洞分析
源碼分析
set_cmnd函數(shù)
File: plugins\sudoers\sudoers.c
800: static int
801: set_cmnd(void)
802: {
? ...
819: ? ? if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { //需要滿足標(biāo)志位的設(shè)置才能進(jìn)入轉(zhuǎn)義的流程
? ...
845:
846: /* set user_args */
847: if (NewArgc > 1) {
848: ? ?char *to, *from, **av;
849: ? ?size_t size, n;
850:
851: ? ?/* Alloc and build up user_args. */
852: ? ?for (size = 0, av = NewArgv + 1; *av; av++) //遍歷每一個(gè)參數(shù)
853: size += strlen(*av) + 1; //計(jì)算每一個(gè)參數(shù)的長(zhǎng)度
854: ? ?if (size == 0 || (user_args = malloc(size)) == NULL) { //通過(guò)malloc動(dòng)態(tài)分配一段內(nèi)存,用于存放參數(shù)內(nèi)容
855: sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
856: debug_return_int(-1);
857: ? }
858: ? ?if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { //需要滿足標(biāo)志位的設(shè)置才能進(jìn)入轉(zhuǎn)義的流程
859: /*
860: * When running a command via a shell, the sudo front-end
861: * escapes potential meta chars. We unescape non-spaces
862: * for sudoers matching and logging purposes.
863: */
864: for (to = user_args, av = NewArgv + 1; (from = *av); av++) { //遍歷每個(gè)環(huán)境變量,并將內(nèi)容拷貝到內(nèi)存中
865: ? ?while (*from) {
? /*
? 漏洞點(diǎn),當(dāng)掃描參數(shù)內(nèi)容時(shí),遇到\需要進(jìn)行轉(zhuǎn)義處理,例如'\t'、'\n'等,因此sudo只判斷\后是否跟隨著空格字符,即用isspace函數(shù)進(jìn)行判 斷。
? isspace包括的字符如下:
? ' ' ? ? (0x20) ? space (SPC) 空格符
'\t' ? (0x09) ? horizontal tab (TAB) 水平制表符 ? ?
'\n' ? (0x0a) ? newline (LF) 換行符
'\v' ? (0x0b) ? vertical tab (VT) 垂直制表符
'\f' ? (0x0c) ? feed (FF) 換頁(yè)符
'\r' ? (0x0d) ? carriage return (CR) 回車符
以上不包括'\0'。
而參數(shù)之間是使用'\0'作為分隔符的,因此當(dāng)'\\'后跟隨的'\0'會(huì)使得from++從而導(dǎo)致將后一個(gè)參數(shù)也被拷貝進(jìn)來(lái),最后致使堆塊溢出。
? */
866: if (from[0] == '\\' && !isspace((unsigned char)from[1]))
867: ? ?from++;
868: *to++ = *from++;
869: ? }
870: ? ?*to++ = ' ';
871: }
872: *--to = '\0';
?
使用POC的例子對(duì)漏洞進(jìn)行說(shuō)明
漏洞原理圖
【----幫助網(wǎng)安學(xué)習(xí),以下所有學(xué)習(xí)資料免費(fèi)領(lǐng)!加vx:yj009991,備注 “博客園” 獲??!】
① 網(wǎng)安學(xué)習(xí)成長(zhǎng)路徑思維導(dǎo)圖
?、?60+網(wǎng)安經(jīng)典常用工具包
③ 100+SRC漏洞分析報(bào)告
?、?150+網(wǎng)安攻防實(shí)戰(zhàn)技術(shù)電子書(shū)
⑤ 最權(quán)威CISSP 認(rèn)證考試指南+題庫(kù)
?、?超1800頁(yè)CTF實(shí)戰(zhàn)技巧手冊(cè)
?、?最新網(wǎng)安大廠面試題合集(含答案)
?、?APP客戶端安全檢測(cè)指南(安卓+IOS)
因此漏洞點(diǎn)在于在進(jìn)入set_cmnd函數(shù)時(shí)需要對(duì)轉(zhuǎn)義字符進(jìn)行轉(zhuǎn)義,但是函數(shù)卻沒(méi)有判斷轉(zhuǎn)義字符作為參數(shù)末尾的情況,即\ + \x00
parse_args函數(shù)
parse_args函數(shù)用于反轉(zhuǎn)義,即參數(shù)中若存在轉(zhuǎn)義字符,會(huì)在每個(gè)轉(zhuǎn)義字符之前增加一個(gè)\
File: src\parse_args.c
592: ? ? if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { //需要滿足標(biāo)志位的設(shè)置才會(huì)進(jìn)入反轉(zhuǎn)義流程
593: char **av, *cmnd = NULL;
594: int ac = 1;
595:
596: if (argc != 0) {
597: ? ?/* shell -c "command" */
598: ? ?char *src, *dst;
599: ? ?size_t cmnd_size = (size_t) (argv[argc - 1] - argv[0]) +
600: strlen(argv[argc - 1]) + 1;
601:
602: ? ?cmnd = dst = reallocarray(NULL, cmnd_size, 2);
603: ? ?if (cmnd == NULL)
604: sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
605: ? ?if (!gc_add(GC_PTR, cmnd))
606: exit(1);
607:
608: ? ?for (av = argv; *av != NULL; av++) {
609: for (src = *av; *src != '\0'; src++) {
610: ? ?/* quote potential meta characters */
611: ? ?if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
612: *dst++ = '\\';
613: ? ?*dst++ = *src;
614: }
615: *dst++ = ' ';
616: ? }
617: ? ?if (cmnd != dst)
618: dst--; ?/* replace last space with a NUL */
619: ? ?*dst = '\0';
620:
621: ? ?ac += 2; /* -c cmnd */
622: }
?
這也是為什么set_cmnd函數(shù)需要對(duì)參數(shù)進(jìn)行轉(zhuǎn)義,因此若先經(jīng)過(guò)parse_args函數(shù)進(jìn)行反轉(zhuǎn)義,后經(jīng)過(guò)set_cmnd函數(shù)進(jìn)行轉(zhuǎn)義,那么sudo是不會(huì)出現(xiàn)漏洞情況的
繞過(guò)檢驗(yàn)
那么如何繞過(guò)set_cmnd函數(shù)直接進(jìn)入parse_args函數(shù),才是漏洞能夠被成功觸發(fā)的關(guān)鍵因素
首先是如何才能過(guò)進(jìn)入set_cmnd函數(shù),sudo會(huì)經(jīng)過(guò)兩重檢測(cè)
-
sudo_mode需要具有MODE_RUN、MODE_EDIT或者M(jìn)ODE_CHECK的標(biāo)志位
-
sudo_mode需要具有MODE_SHELL或者M(jìn)ODE_LOGIN_SHELL的標(biāo)志位
File: plugins\sudoers\sudoers.c
...
819: ? ? if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { //需要滿足標(biāo)志位的設(shè)置才能進(jìn)入轉(zhuǎn)義的流程
? ...
858: ? ?if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { //需要滿足標(biāo)志位的設(shè)置才能進(jìn)入轉(zhuǎn)義的流程
想要獲得MODE_SHELL的標(biāo)志位,則需要設(shè)置-s參數(shù),此時(shí)通過(guò) SET(flags, MODE_SHELL),將flag設(shè)置上MODE_SHELL,并且默認(rèn)的mode是為NULL,因此設(shè)置-s參數(shù)可以使得flag即設(shè)置MODE_SHELL又設(shè)置MODE_RUN。
File: src\parse_args.c
479: case 's':
480: ? ?sudo_settings[ARG_USER_SHELL].value = "true";
481: ? ?SET(flags, MODE_SHELL);
482: ? ?break;
...
534: if (!mode)
535: ? ?mode = MODE_RUN; /* running a command */
536: ? ? }
但是若使用sudo -s,那么就會(huì)導(dǎo)致flag即設(shè)置MODE_SHELL又設(shè)置MODE_RUN,就會(huì)進(jìn)入parse_args函數(shù)的流程,該流程會(huì)把所有非字母數(shù)字的字符前方增加一個(gè)'\',那么就會(huì)導(dǎo)致我們無(wú)法構(gòu)造'' + '\x00'的漏洞字符,因此想要漏洞利用成功,我們不需要程序進(jìn)入set_cmd函數(shù),但是不能進(jìn)入parse_args函數(shù)
File: src\parse_args.c
592: ? ? if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { //需要滿足標(biāo)志位的設(shè)置才會(huì)進(jìn)入反轉(zhuǎn)義流程
? ...
608: ? ?for (av = argv; *av != NULL; av++) {
609: for (src = *av; *src != '\0'; src++) {
610: ? ?/* quote potential meta characters */
611: ? ?if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
612: *dst++ = '\\';
613: ? ?*dst++ = *src;
614: }
? ...
622: }
在parse_args函數(shù)的開(kāi)頭,會(huì)檢測(cè)是以sudo還是以sudoedit進(jìn)行調(diào)用,若使用sudoedit調(diào)用,那么會(huì)直接給mode設(shè)置上MODE_EDIT,從而繞過(guò)了mode==NULL時(shí),需要將flag設(shè)置為MODE_RUN,因此使用sudoedit -s,可以使得flag即設(shè)置MODE_EDIT又設(shè)置MODE_SHELL
File: src\parse_args.c
? ...
265: ? ? proglen = strlen(progname);
266: ? ? if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
267: progname = "sudoedit";
268: mode = MODE_EDIT;
269: sudo_settings[ARG_SUDOEDIT].value = "true";
270: ? ? }
想要進(jìn)入set_cmnd第二條路徑就是flag設(shè)置為MODE_EDIT | MODE_SHELL,這樣的輸入就能夠繞過(guò)parse_args函數(shù)而禁止進(jìn)入set_cmd函數(shù),這也是為什么sudo的堆溢出,需要使用sudoedit -s觸發(fā),而不是sudo -s
File: plugins\sudoers\sudoers.c
...
819: ? ? if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { //需要滿足標(biāo)志位的設(shè)置才能進(jìn)入轉(zhuǎn)義的流程
? ...
858: ? ?if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { //需要滿足標(biāo)志位的設(shè)置才能進(jìn)入轉(zhuǎn)義的流程
漏洞利用
漏洞利用分析
由于程序存在一個(gè)明顯的堆溢出漏洞,因此需要梳理一下堆溢出如何進(jìn)行利用。
? 找到一個(gè)堆塊,該堆塊的值會(huì)影響程序執(zhí)行的流程,這里稱之為可利用堆塊。
? 找到可以隨意控制堆塊位置的操作,將漏洞函數(shù)申請(qǐng)的堆塊部署在可利用堆塊的上方,當(dāng)堆溢出觸發(fā)時(shí),可以將可利用堆塊的值被改寫成我們預(yù)期的值。
可利用堆塊
nss是用于解析和獲取不同類型的名稱信息,例如如何通過(guò)用名稱去獲取用戶信息,在sudo需要獲取用戶信息時(shí)則需要調(diào)用nss。
在使用nss去獲取信息時(shí),其實(shí)是通過(guò)不同的動(dòng)態(tài)鏈接庫(kù)去執(zhí)行相應(yīng)的行為,而這些庫(kù)的文件名則存在于/etc/nsswitch.conf的配置文件中
例如想要查詢passwd文件則需要用到libnss_files.so與libnss_systemed.so
那么如何加載這些動(dòng)態(tài)鏈接庫(kù)則需要依賴于nss_load_library函數(shù),而且這些相關(guān)信息都被存放在service_user結(jié)構(gòu)體中,而該結(jié)構(gòu)體是存放在堆內(nèi)存中的。
接著得先研究該結(jié)構(gòu)體的值是否會(huì)影響程序的執(zhí)行流程,代碼如下。
File: nsswitch.c
327: static int
328: nss_load_library (service_user *ni)
329: {
330: ? if (ni->library == NULL)
331: ? ? {
332: ? ? ? /* This service has not yet been used. Fetch the service
333: library for it, creating a new one if need be. If there
334: is no service table from the file, this static variable
335: holds the head of the service_library list made from the
336: default configuration. */
337: ? ? ? static name_database default_table;
338: ? ? ? ni->library = nss_new_service (service_table ?: &default_table,
339: ? ? ni->name); //若ni->library的值為NULL,那么就會(huì)新建一個(gè)ni->library并將成員都進(jìn)行初始化
340: ? ? ? if (ni->library == NULL)
341: return -1;
342: ? ? }
343:
344: ? if (ni->library->lib_handle == NULL) //由于ni->library剛新建,因此ni->library->lib_handle必定為NULL
345: ? ? {
346: ? ? ? /* Load the shared library. */
347: ? ? ? size_t shlen = (7 + strlen (ni->name) + 3
348: ? ? ?+ strlen (__nss_shlib_revision) + 1);
349: ? ? ? int saved_errno = errno;
350: ? ? ? char shlib_name[shlen];
351:
352: ? ? ? /* Construct shared object name. */
353: ? ? ? __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
354: ? ? ?"libnss_"),
355: ? ?ni->name),
356: ?".so"), //shalib_name是根據(jù)拼接得到
357: __nss_shlib_revision);
358:
359: ? ? ? ni->library->lib_handle = __libc_dlopen (shlib_name); //加載動(dòng)態(tài)鏈接庫(kù)
?
上述代碼有個(gè)非常關(guān)鍵的點(diǎn)在于,程序會(huì)使用__libc_dlopen打開(kāi)shalib_name指定的動(dòng)態(tài)鏈接庫(kù),而shalib_name是通過(guò)ni->name進(jìn)行一系列的拼接得到,而ni->name則是存放在結(jié)構(gòu)體service_user *ni中的,該結(jié)構(gòu)體又是存放在堆內(nèi)存中的。那么我們就找到了關(guān)鍵的值ni->name,它是能夠完成修改程序執(zhí)行流程的關(guān)鍵變量。
舉個(gè)例子,例如我們將ni->name修改為X/test,那么最后拼接的結(jié)果會(huì)得到libnss_X/test.so,那么如果我們?cè)诋?dāng)前目錄下新建一個(gè)libnss_X并且在該目錄中創(chuàng)建一個(gè)test.so的動(dòng)態(tài)鏈接庫(kù),那么sudo就會(huì)加載并執(zhí)行我們動(dòng)態(tài)鏈接庫(kù)中的代碼。至此我們找到利用的第一個(gè)關(guān)鍵因素,可利用堆塊。
布置堆塊的操作
由于我們已經(jīng)找到了可利用的堆塊,如果能夠?qū)⒍岩绯龅亩褖K部署在可利用堆塊的上方,在利用堆溢出修改ni->name,即可完成任意代碼執(zhí)行的效果。
在sudo的main函數(shù)中,會(huì)執(zhí)行setlocate函數(shù)。setlocale 是一個(gè)用于設(shè)置程序的區(qū)域設(shè)置(locale)的函數(shù),在許多編程語(yǔ)言和操作系統(tǒng)中都有對(duì)應(yīng)的實(shí)現(xiàn)。
區(qū)域設(shè)置是指程序在運(yùn)行時(shí)所采用的語(yǔ)言、地區(qū)、日期格式、貨幣符號(hào)等相關(guān)信息的集合。通過(guò)設(shè)置區(qū)域設(shè)置,程序可以根據(jù)不同的地區(qū)和語(yǔ)言環(huán)境來(lái)適應(yīng)本地化需求。
export LC_ALL=en_US.UTF-8@XXXX
而在setlocal函數(shù)中涉及十分多的堆塊分配與釋放的操作,當(dāng)調(diào)用setlocal(LC_ALL,"")時(shí),程序會(huì)通過(guò)環(huán)境變量設(shè)置的值去搜索區(qū)域設(shè)置的值,而環(huán)境變量的搜索則依靠_nl_find_locale函數(shù)。
_nl_find_locale函數(shù)
File: locale\findlocale.c
101: struct __locale_data *
102: _nl_find_locale (const char *locale_path, size_t locale_path_len,
103: int category, const char **name)
104: {
? ...
184: ? /* LOCALE can consist of up to four recognized parts for the XPG syntax:
185:
186: language[_territory[.codeset]][@modifier]
187:
188: ? ? Beside the first all of them are allowed to be missing. If the
189: ? ? full specified locale is not found, the less specific one are
190: ? ? looked for. The various part will be stripped off according to
191: ? ? the following order:
192: (1) codeset
193: (2) normalized codeset
194: (3) territory
195: (4) modifier
196: ? */
? ? ? /*
? ? ? 區(qū)域的格式為C_en_US.UTF-8@XXXXXX
? ? ? _nl_explode_name用于判斷(1)(2)(3)(4)哪部分存在,哪部分缺失
? ? ? */
197: ? mask = _nl_explode_name (loc_name, &language, &modifier, &territory,
198: ? &codeset, &normalized_codeset);
199: ? if (mask == -1)
200: ? ? /* Memory allocate problem. */
201: ? ? return NULL;
202:
? //locale_file則給區(qū)域設(shè)置進(jìn)行動(dòng)態(tài)內(nèi)存的分配
205: ? locale_file = _nl_make_l10nflist (&_nl_locale_file_list[category],
206: ? ?locale_path, locale_path_len, mask,
207: ? ?language, territory, codeset,
208: ? ?normalized_codeset, modifier,
209: ? ?_nl_category_names_get (category), 0); //返回NULL
210:
211: ? if (locale_file == NULL)
212: ? ? {
213: ? ? ? /* Find status record for addressed locale file. We have to search
214: through all directories in the locale path. */
215: ? ? ? locale_file = _nl_make_l10nflist (&_nl_locale_file_list[category],
216: locale_path, locale_path_len, mask,
217: language, territory, codeset,
218: normalized_codeset, modifier,
219: _nl_category_names_get (category), 1);
220: ? ? ? if (locale_file == NULL)
221: /* This means we are out of core. */
222: return NULL;
223: ? ? }
}
_nl_make_l10nflist**函數(shù)**
_nl_make_l10nflist會(huì)根據(jù)我們傳入的值進(jìn)行堆塊的分配。
File: intl\l10nflist.c
150: struct loaded_l10nfile *
151: _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
152: ? ?const char *dirlist, size_t dirlist_len,
153: ? ?int mask, const char *language, const char *territory,
154: ? ?const char *codeset, const char *normalized_codeset,
155: ? ?const char *modifier,
156: ? ?const char *filename, int do_allocate)
157: {
? ...
165: ? //根據(jù)我們傳入的區(qū)域值的長(zhǎng)度進(jìn)行動(dòng)態(tài)分配
166: ? abs_filename = (char *) malloc (dirlist_len
167: ?+ strlen (language)
168: ?+ ((mask & XPG_TERRITORY) != 0
169: ? ? ? strlen (territory) + 1 : 0)
170: ?+ ((mask & XPG_CODESET) != 0
171: ? ? ? strlen (codeset) + 1 : 0)
172: ?+ ((mask & XPG_NORM_CODESET) != 0
173: ? ? ? strlen (normalized_codeset) + 1 : 0)
174: ?+ ((mask & XPG_MODIFIER) != 0
175: ? ? ? strlen (modifier) + 1 : 0)
176: ?+ 1 + strlen (filename) + 1);
177:
? ...
292: }
?
setlocale**函數(shù)**
setlocale函數(shù)總體操作則是讀取環(huán)境變量的值獲取區(qū)域設(shè)置的值,根據(jù)區(qū)域設(shè)置的值分配堆塊大小,若其中存在不符合區(qū)域值的規(guī)范,則會(huì)將所有先前申請(qǐng)的堆塊都釋放掉。
File: locale\setlocale.c
334: ? ? ? while (category-- > 0)
335: if (category != LC_ALL)
336: {
? //通過(guò)_nl_find_locale函數(shù)去獲取環(huán)境變量的值,存放在newdata[category]中
337: ? ?newdata[category] = _nl_find_locale (locale_path, locale_path_len,
338: category,
339: &newnames[category]);
340:
...
364: else
365: {
? //使用__strdup函數(shù)在堆內(nèi)存中分配空間,并將newdata[category]拷貝進(jìn)去
366: ? ?newnames[category] = __strdup (newnames[category]);
367: ? ?if (newnames[category] == NULL)
368: ? ? ?break;
369: }
? ...
393: ?if (category != LC_ALL && newnames[category] != _nl_C_name
394: ? ? ?&& newnames[category] != _nl_global_locale.__names[category])
395: ? ?free ((char *) newnames[category]); //這里就是堆塊釋放的原語(yǔ)了,只要有一個(gè)區(qū)域設(shè)置的值不符合規(guī)范,則將之前所有申請(qǐng)的堆塊都釋放掉
?
因此可以通過(guò)區(qū)域值去控制堆塊的大小,接著在最后設(shè)置一個(gè)錯(cuò)誤的區(qū)域值去控制堆塊的位置,至此我們找到可控制堆塊的操作。
LC_IDENTIFICATION = C.UTF-8@XX..XX #若長(zhǎng)度為0x10,則malloc(0x10) LC_MEASUREMENT = C.UTF-8@XX..XXX,#若長(zhǎng)度為0X20,則malloc(0x20) LC_TELEPHONE = XXXX #不符合區(qū)域值的規(guī)范,則會(huì)調(diào)用free()
exp的分析
由于我們需要控制server_user的堆塊,因此需要知道該堆塊的大小為多少,通過(guò)調(diào)試可知是0x40的堆塊,因此利用setlocate多釋放幾個(gè)0x40的堆塊,那么server_user就會(huì)使用到我們所釋放的堆塊。
緊接著將漏洞堆塊分配到server_user堆塊的上方,由于server_user的堆塊是我們自己構(gòu)建的,因此只需要在釋放該堆塊的同時(shí)也釋放漏洞堆塊即可,并且漏洞堆塊的申請(qǐng)可是根據(jù)參數(shù)的長(zhǎng)度所設(shè)置的
將設(shè)置區(qū)域值的函數(shù)設(shè)置為堆塊分配與釋放的原語(yǔ),使用@后面的字符控制堆塊的大小
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-505206.html
使用錯(cuò)誤的區(qū)域值進(jìn)行堆塊的釋放
最后就是如何填充到可利用堆塊,這里使用堆溢出,并且在環(huán)境變量中構(gòu)造填充字符串,使得漏洞堆塊可以覆蓋掉可利用堆塊的內(nèi)容值,但這里需要注意的是,我們需要將ni->library中用\x00填充,而\x00是無(wú)法直接輸入到環(huán)境變量中的,因此需要再次觀察漏洞函數(shù)是如何拷貝字符的。根據(jù)代碼分析可知,只要''后緊跟著'\x00',那么我們就能將\x00的值直接拷貝的堆內(nèi)存中。緊接著將ni->name修改為我們認(rèn)為構(gòu)造的動(dòng)態(tài)鏈接庫(kù)即可。
File: plugins\sudoers\sudoers.c
866: if (from[0] == '\\' && !isspace((unsigned char)from[1])) //若 '\' 后跟著'\x00'
867: ? ?from++; //此時(shí)from會(huì)指向\x00
868: *to++ = *from++; //使用\x00進(jìn)行值的拷貝
869: ? }
設(shè)置多個(gè)環(huán)境變量使得內(nèi)存存在多個(gè)'' + '\x00',從而使用'\x00'去覆蓋堆的內(nèi)存值。
演示效果如下
漏洞修復(fù)
漏洞的修復(fù)則是將MODE_EDIT的標(biāo)志位進(jìn)行了額外的判斷,并且在''后面增加了對(duì)'\0'的校驗(yàn)
?
--- a/plugins/sudoers/sudoers.c Sat Jan 23 08:43:59 2021 -0700
+++ b/plugins/sudoers/sudoers.c Sat Jan 23 08:43:59 2021 -0700
@@ -547,7 +547,7 @@
? ? /* If run as root with SUDO_USER set, set sudo_user.pw to that user. */
? ? /* XXX - causes confusion when root is not listed in sudoers */
- ? ?if (sudo_mode & (MODE_RUN | MODE_EDIT) && prev_user != NULL) {
+ ? ?if (ISSET(sudo_mode, MODE_RUN|MODE_EDIT) && prev_user != NULL) {
if (user_uid == 0 && strcmp(prev_user, "root") != 0) {
? ?struct passwd *pw;
@@ -932,8 +932,8 @@
? ? if (user_cmnd == NULL)
user_cmnd = NewArgv[0];
- ? ?if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
- if (ISSET(sudo_mode, MODE_RUN | MODE_CHECK)) {
+ ? ?if (ISSET(sudo_mode, MODE_RUN|MODE_EDIT|MODE_CHECK)) {
+ if (!ISSET(sudo_mode, MODE_EDIT)) { //對(duì)MODE_EDIT進(jìn)行了額外的判斷
? ?const char *runchroot = user_runchroot;
? ?if (runchroot == NULL && def_runchroot != NULL &&
? ?strcmp(def_runchroot, "*") != 0)
@@ -961,7 +961,8 @@
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(NOT_FOUND_ERROR);
? }
- ? ?if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
+ ? ?if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL) &&
+ ? ?ISSET(sudo_mode, MODE_RUN)) { //需要sudo -s才能進(jìn)行轉(zhuǎn)義
/*
* When running a command via a shell, the sudo front-end
* escapes potential meta chars. We unescape non-spaces
@@ -969,10 +970,22 @@
*/
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
? ?while (*from) {
- if (from[0] == '\\' && !isspace((unsigned char)from[1]))
+ if (from[0] == '\\' && from[1] != '\0' && ?//增加了'\0'的判斷
+ !isspace((unsigned char)from[1])) {
? ?from++;
+ }
+ if (size - (to - user_args) < 1) {
+ ? ?sudo_warnx(U_("internal error, %s overflow"),
+ __func__);
+ ? ?debug_return_int(NOT_FOUND_ERROR);
+ }
*to++ = *from++;
? }
+ ? ?if (size - (to - user_args) < 1) {
+ sudo_warnx(U_("internal error, %s overflow"),
+ ? ?__func__);
+ debug_return_int(NOT_FOUND_ERROR);
+ ? }
? ?*to++ = ' ';
}
*--to = '\0';
總結(jié)
Sudo堆溢出攻擊流程
首先利用setlocate作為堆塊分配與釋放的原語(yǔ),構(gòu)造出適合的堆布局確保server_user堆塊盡可能貼近漏洞代碼開(kāi)辟出來(lái)的堆塊。
其次利用堆溢出將server_user堆塊的ni->name值覆蓋,覆蓋的值為惡意構(gòu)造的動(dòng)態(tài)鏈接庫(kù)名。
最后等待動(dòng)態(tài)鏈接庫(kù)被加載執(zhí)行。
Sudo堆溢出利用的限制
由于sudo堆溢出依賴堆的布局,因此不同版本的sudo或者操作系統(tǒng)都會(huì)影響漏洞的利用。
更多網(wǎng)安技能的在線實(shí)操練習(xí),請(qǐng)點(diǎn)擊這里>>文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-505206.html
?
到了這里,關(guān)于Sudo堆溢出漏洞(CVE-2021-3156)復(fù)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!