TOTOLINK A7000R路由器固件调试
近期在尝试挖路由器设备的漏洞,选中了Totolink这一比较好挖洞的牌子,有一些已知漏洞想要复现一下,需要使用gdb进行调试,因此在此记录下具体过程。
参考链接:
模拟执行目标固件
固件为MIPS架构,可以直接使用binwalk提取出文件系统。
本次选择的目标为A7000R型号其中的/cgi-bin/cstecgi.cgi
文件。
使用qemu用户模式执行/usr/sbin/lighttpd
(lighttpd与cgi文件的关系可看上面给的参考链接)
1 | cp xx/qemu-mipsel-static ./ |
然后在宿主机执行gdb进行调试
1 | gdb-multiarch ./usr/sbin/lighttpd |
然后就可以进入调试界面了
gdb配置
需要注意的是,漏洞存在于/cgi-bin/cstecgi.cgi
文件中,该文件由lighttpd
使用fork子进程的方式完成调用。因此还需要在gdb中进行额外的设置来调试cstecgi.cgi
文件的漏洞。
1 | set follow-fork-mode child |
首先在lighttpd
的main函数下断点。确保调试正常
然后寻找fork函数调用点,极有可能为cgi的启动函数,下图为在proc_open中找到的fork+execve函数,猜测可能是cgi-bin的启动函数
在execl函数处下断点,向lighttpd
程序发送poc请求包,发现在令lighttpd继续执行后,其会直接退出,因此考虑其他方法
gdb attach 进程
lighttpd虽然退出,但是还存在进程在监听80端口,同时web服务正常,因此考虑attach对应进程
1 | sudo netstat -antp | grep 80 |
但是和之前一样,attach的是qemu进程,并不是lighttpd
直接调试cgi
cgi程序运行
根据参考链接可以知道,如果为cgi文件提供正确的环境变量,也是可以直接运行cgi文件的。
如下图,直接运行cgi文件会报错:
因此考虑为cgi提供合适的环境变量,来模拟运行,通过逆向cgi的main函数处理逻辑,得到以下环境变量
1 | export QUERY_STRING=http://127.0.0.1/index.html # 请求url,可以触发loginAuth,Upload处理逻辑 |
添加以上环境变量后,发现不再报错:
使用gdb-multiarch调试,成功断在main函数位置
1 | gdb-multiarch ./www/cgi-bin/cstecgi.cgi |
继续逆向会发现cgi程序接受参数输入的方法有两种:
环境变量
getenv
:从lighttpd
程序设置好的环境变量中提取参数值标准输入
fread
:从标准输入获取json变量内容,适用于WebGetsVar()
函数。如下图所示,v11为标准输入获取的值
cgi程序分析
此处再简要概括下cgi文件main函数的主要逻辑:
首先获取环境变量,分配
CONTENT_LENGTH
大小空间存储输入的JSON数据,然后判断QUERY_STRING值是否存在”action=login”和”action=upload”字符串,有则继续,没有则转入3如果存在”action=login”,则设置为
topicurl
为”loginAuth”,并构造请求包1
2
3
4{
"topicurl" : "loginAuth",
"loginAuthUrl": "{arg1}&http_host={arg2}&flag=ie8",
}如果存在”action=upload”,则继续获取环境变量UPLOAD_FILENAME,设置
topicurl
为QUERY_STRING第一个参数,并设置FileName等参数变量1
2
3
4
5
6{
"topicurl" : "{arg1}",
"FileName" : "{arg2}",
"ContentLength": "/tmp/linux.trx",
...
}获取标准输入解析出”topicurl”值,并根据”set”, “get”, “del”分类跳转到不同的handler列表,执行对应的回调函数。
漏洞复现
我们要测试的漏洞位于setWizardCfg
函数,因此只需要构造以下环境变量:
1 | export CONTENT_LENGTH=52 |
继续执行到fread函数,读取输入为请求参数:
输入以下payload:
1 | {"topicurl":"setWizardCfg", "ssid5g":"aaaaaaaaaa"} |
注意CONTENT_LENGTH与payload长度要对应:
1 | import json |
读入后,我们在setWizardCfg函数设置断点,查看是否正常调用该函数:
成功断在该位置。且成功读取了我们设置的”ssid5g”参数。
然后我们就可以进行调试了,通过构造长度足够大的”ssid5g”参数,造成缓冲区溢出:
poc.py
1 | # export CONTENT_LENGTH=4138 |
执行后断在sink函数,发现成功覆盖fp, sp寄存器后
输入过长payload造成栈溢出