CNVD-2018-01084复现(D-Link DIR部分型号service.cgi命令执行)

前言

跟着 winmt 师傅的私房笔记做了一个 D-Link 的命令注入漏洞复现( CNVD-2018-01084),由于前一段做了一个 CVE-2018-7034复现–TrendNet路由器登录信息泄露 ,那个固件当时进行了仔细分析,而这次的 DIR-815TrendNet 里的 cgibin 几乎一致 ,因此只针对漏洞利用部分进行了详细分析。

漏洞信息

漏洞信息

D-Link DIR 615/645/815路由器1.03及之前的固件版本存在远程命令执行漏洞。该漏洞是由于service.cgi中拼接了HTTP POST请求中的数据,造成后台命令拼接,导致可执行任意命令。

固件下载

根据漏洞描述来看,必须要寻找固件版本为 1.03 及之前的 下载链接

程序分析

servicecgi_main 函数中存在一个 lxldbc_system 函数

image-20231115125329494

这里的 va_start 就是来接收传进来的可变参数,然后用 vsnprintf 函数解析字符串拼接成一个字符串,用 system 将其执行。

确定一下其参数是否可控,有的 IDA 里看到的可能是下图这种界面,这里其实 IDA 没显示完全

image-20231115132207531

我是多按了几次 F5 就刷新成了下图的正确形式

image-20231115131929901

即使 F5 刷不出来正常的伪代码,查看此处汇编代码也能发现是传了两个参数, $a0$a1 寄存器都有值,对 $a1 的值进行简单的追踪也可以分析出 lxmldbc_system 函数的第二个参数

image-20231115135149369

lxmldbc_system 函数的第二个参数进行分析,这两处无论走哪个,都绕不开 sub_40A1C0 函数。看起来似乎是解析了个什么字段?得具体分析一下 sub_40A1C0 函数

image-20231115140447926

sub_40A1C0函数分析

int __fastcall sub_40A1C0(const char *a1)
{
void *v2; // $s1
int v3; // $s0
v2 = off_42C120;
v3 = 0;
while ( 1 )
{
v3 = (int)(v3 ? *(_DWORD *)v3 : v2);
if ( (void **)v3 == &off_42C120 || !v3 )
break;
if ( !strcmp(a1, *(const char **)(v3 + 8)) )
return *(_DWORD *)(v3 + 12);
}
return 0;
}

开始就是 while 死循环, v3 = (int)(v3 ? *(_DWORD *)v3 : v2) 这是一个三目运算符,如果 v3 为真的话就将 *v3 的值赋给 v3v3 为假则将 v2 赋值给它。因为 v3 初值是 0 ,所以第一次进入循环 v3 取的就是地址 0x42C120if ( (void **)v3 == &off_42C120 || !v3 ) 在判断 0x42C120 里是否为空,如果为空的话就直接 break 结束循环,然后 return 0

如果 v3 里不为空,则比较 a1v3+8 的值(a1 是函数 sub_40A1C0 传进来的字符串),二者一样的话则返回 *(v3+12) 。分析到这里后,应该能发现这个函数是做了一个遍历链表的操作。

0x42C120 地址里装入一个链表头指针,大概一个结点的结构是这样

struct Note {
void *next_ptr;//while循环通过遍历next_ptr,判断下一个结点
int xxx;//该字段无法根据此处代码猜测出来,但能大概确定此处有一个四字节类型的变量
char *key;//该字段与sub_40A1C0函数的参数 EVENT/ACTION/SERVICE 做对比
char *value;//如果key字段比较通过,返回value
};

这里是对 0x42C120 链表进行的检查和取值操作,这一步之前一定会有地方对其进行了创建链表和写入操作。

对地址 0x42C120 进行交叉引用,发现了三处操作指令(如下)分别是两次 lw 和一次 sw 指令。sw $s0,off_42C120 指令是将 $s0 寄存器里的值写到内存 0x42C120 处,因此肯定是这个位置进行链表的创建操作

image-20231115145801145

跳转至此,发现位于 sub_40A63C 函数,这里显然是进行的初始化操作,参考这篇文章 里的分析,当时我提到该函数是一个回调函数。由于调用关系有些错综复杂,动态调试到此处看一下解析后的字段更方便。

注:在 sub_403B10 函数中有 select 函数,它检查了输入缓冲区中是否存在数据(比如 CONTENT_LENGTH 环境变量设置为 10 ,走到这里为了防止卡住就得输入 10 个字符)具体分析可以参考 文章

image-20231115170912006

调试验证

qemu 启动脚本如下

sudo qemu-mipsel -g 1234 -L .\
-0 "service.cgi" \
-E REQUEST_METHOD="POST" \
-E REQUEST_URI="wtf?EVENT=;ls;" \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E CONTENT_LENGTH="5" \
./htdocs/cgibin

我选择将断点下到 0x40A6D4 0x40A6B4 0x40A6D0 三个位置,分别查看上面分析的三个字段 next_ptr key value 是什么。

结果发现了一个有意思的情况,提示说断点 0x40A6B4 调整到了 0x40a6b0

image-20231115173322252

查看 IDA ,发现 0x40A6B4 地址是 jalr 指令的下一条,推断应该是受到 MIPS 架构中的分支延迟槽的影响。

image-20231115173531720

执行到地址 0x40a6b8 时,查看此时的 $s0 寄存器,因为 sw $v0,8($s0) 指令是向 $s0+8 的位置写入了值,用 telescope 很清楚的能看出来放的是 aaa 字符串的地址

image-20231115174525763

等到对结构体初始化后进行查看,结合传入的环境变量 REQUEST_URI="wtf?aaa=123" 可以很清楚的分析出 REQUEST_URI 中的 ? 做为第一个分隔符。? 后面的内容被 = 再次分割,= 前面的值记录在了偏移 8 的地方,= 后面的值记录在了偏移 12 的地方

image-20231115174919568

既然初始化的格式确定了,那么让 sub_40A1C0 函数解析出对应字段的值便轻而易举。以下图中 43 行代码为例,环境变量设置为 REQUEST_URI="wtf?EVENT=;ls;" 便可以进行命令注入,成功执行 ls ,但前提是要通过 sess_ispoweruser 函数的认证。很显然仿真阶段对于认证函数必须得想办法绕过,来验证漏洞能否利用。可以选择在 IDA 中直接 patch 文件,将认证函数改成 nop

image-20231115175458559

不过为了省事,我选择写了一个 shell 脚本,gdb 调试时,直接用 set 修改了认证函数的返回值

set endian little
set architecture mips
target remote localhost:1234
b *0x40a3a0
c
set $v0=1
b *0x40A40C
c
image-20231115180827803

直接 q 退出 gdb 调试,发现 qemu 启的程序这边已经成功执行了 ls 命令

image-20231115181030132