CNVD-2018-01084复现(D-Link DIR部分型号service.cgi命令执行)
前言
跟着 winmt 师傅的私房笔记做了一个 D-Link
的命令注入漏洞复现( CNVD-2018-01084),由于前一段做了一个 CVE-2018-7034复现–TrendNet路由器登录信息泄露 ,那个固件当时进行了仔细分析,而这次的 DIR-815
和 TrendNet
里的 cgibin
几乎一致 ,因此只针对漏洞利用部分进行了详细分析。
漏洞信息
D-Link DIR 615/645/815路由器1.03及之前的固件版本存在远程命令执行漏洞。该漏洞是由于service.cgi中拼接了HTTP POST请求中的数据,造成后台命令拼接,导致可执行任意命令。
固件下载
根据漏洞描述来看,必须要寻找固件版本为 1.03
及之前的 下载链接
程序分析
在 servicecgi_main
函数中存在一个 lxldbc_system
函数
这里的 va_start
就是来接收传进来的可变参数,然后用 vsnprintf
函数解析字符串拼接成一个字符串,用 system
将其执行。
确定一下其参数是否可控,有的 IDA
里看到的可能是下图这种界面,这里其实 IDA
没显示完全
我是多按了几次 F5
就刷新成了下图的正确形式
即使 F5
刷不出来正常的伪代码,查看此处汇编代码也能发现是传了两个参数, $a0
和 $a1
寄存器都有值,对 $a1
的值进行简单的追踪也可以分析出 lxmldbc_system
函数的第二个参数
对 lxmldbc_system
函数的第二个参数进行分析,这两处无论走哪个,都绕不开 sub_40A1C0
函数。看起来似乎是解析了个什么字段?得具体分析一下 sub_40A1C0
函数
sub_40A1C0函数分析
int __fastcall sub_40A1C0(const char *a1) |
开始就是 while
死循环, v3 = (int)(v3 ? *(_DWORD *)v3 : v2)
这是一个三目运算符,如果 v3
为真的话就将 *v3
的值赋给 v3
,v3
为假则将 v2
赋值给它。因为 v3
初值是 0
,所以第一次进入循环 v3
取的就是地址 0x42C120
。if ( (void **)v3 == &off_42C120 || !v3 )
在判断 0x42C120
里是否为空,如果为空的话就直接 break
结束循环,然后 return 0
如果 v3
里不为空,则比较 a1
和 v3+8
的值(a1
是函数 sub_40A1C0
传进来的字符串),二者一样的话则返回 *(v3+12)
。分析到这里后,应该能发现这个函数是做了一个遍历链表的操作。
将 0x42C120
地址里装入一个链表头指针,大概一个结点的结构是这样
struct Note { |
这里是对 0x42C120
链表进行的检查和取值操作,这一步之前一定会有地方对其进行了创建链表和写入操作。
对地址 0x42C120
进行交叉引用,发现了三处操作指令(如下)分别是两次 lw
和一次 sw
指令。sw $s0,off_42C120
指令是将 $s0
寄存器里的值写到内存 0x42C120
处,因此肯定是这个位置进行链表的创建操作
跳转至此,发现位于 sub_40A63C
函数,这里显然是进行的初始化操作,参考这篇文章 里的分析,当时我提到该函数是一个回调函数。由于调用关系有些错综复杂,动态调试到此处看一下解析后的字段更方便。
注:在 sub_403B10
函数中有 select
函数,它检查了输入缓冲区中是否存在数据(比如 CONTENT_LENGTH
环境变量设置为 10
,走到这里为了防止卡住就得输入 10
个字符)具体分析可以参考 文章
调试验证
qemu
启动脚本如下
sudo qemu-mipsel -g 1234 -L .\ |
我选择将断点下到 0x40A6D4
0x40A6B4
0x40A6D0
三个位置,分别查看上面分析的三个字段 next_ptr
key
value
是什么。
结果发现了一个有意思的情况,提示说断点 0x40A6B4
调整到了 0x40a6b0
查看 IDA
,发现 0x40A6B4
地址是 jalr
指令的下一条,推断应该是受到 MIPS
架构中的分支延迟槽的影响。
执行到地址 0x40a6b8
时,查看此时的 $s0
寄存器,因为 sw $v0,8($s0)
指令是向 $s0+8
的位置写入了值,用 telescope
很清楚的能看出来放的是 aaa
字符串的地址
等到对结构体初始化后进行查看,结合传入的环境变量 REQUEST_URI="wtf?aaa=123"
可以很清楚的分析出 REQUEST_URI
中的 ?
做为第一个分隔符。?
后面的内容被 =
再次分割,=
前面的值记录在了偏移 8
的地方,=
后面的值记录在了偏移 12
的地方
既然初始化的格式确定了,那么让 sub_40A1C0
函数解析出对应字段的值便轻而易举。以下图中 43
行代码为例,环境变量设置为 REQUEST_URI="wtf?EVENT=;ls;"
便可以进行命令注入,成功执行 ls
,但前提是要通过 sess_ispoweruser
函数的认证。很显然仿真阶段对于认证函数必须得想办法绕过,来验证漏洞能否利用。可以选择在 IDA
中直接 patch
文件,将认证函数改成 nop
。
不过为了省事,我选择写了一个 shell
脚本,gdb
调试时,直接用 set
修改了认证函数的返回值
set endian little |
直接 q
退出 gdb
调试,发现 qemu
启的程序这边已经成功执行了 ls
命令