在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_COOKIE
和REMOTE_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/
后应该设置成什么,也就需要知道这里进入的未知函数是什么:
看到对应的汇编:
最后的跳转命令在0x40346C
处,因此,我们在这里下一个断点。
先用下面的命令启动qemu
用户模式:
再用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"
的形式,启动命令如下:
在sub_40A1C0
中strcmp
的地方对应的汇编处(0x40A200
)下一个断点:
在之前随便输入十个字符,最后停止了断点处:
由此可知,strcmp
的第二个参数就是环境变量REQUEST_URI
中?
与=
之间的字符串。
用同样的方法,可以得到:lxmldbc_system
中拼接入格式化字符串的va
参数就是环境变量REQUEST_URI
中=
之后的字符串。
至此,我们完成了对/htdocs/cgibin
这个二进制文件中的漏洞分析,显然,我们将;{cmd};
拼接进格式化字符串,由于;
可连接两个独立语句并执行,这样就能执行我们的cmd
命令了。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-4-7 17:25
被winmt编辑
,原因: