记一次失败的漏洞挖掘过程

前言

本文记录了一次失败的漏洞挖掘经历,但正是因为自己都在进行主观的思考,即使是失败了,这一次也学到了一些东西。我认为跟着别人文章照猫画虎成功做一遍,不如失败的独立思考探索有意义😎

事情的起因是想去复现一下 D-Link DIR-825TRENDnet TEW-632BRP 命令注入漏洞 (CVE-2020-10216 )。因为一直没找到命令注入的点,一时兴起,就随便看了看 DIR-825 固件中的 cgibin 。大致看了一下后,又想着下载一个型号相近的 DIR-850 看看 cgibin 里有没有什么漏洞(我希望在不看漏洞披露信息的情况下,寻找一些比较明显的漏洞)

固件下载

在此之前,先记录下固件下载的过程,以 D-Link DIR-825 固件为例,首先我在搜索引擎中输入 D-Link support

image-20231029103357295

这里我是直接搜了 DIR-825

image-20231029103717873

居然没有搜到……这里没有显示这个版本的路由器

image-20231029103856201

然后我又换了一种搜法 D-Link DIR-825 firmware

image-20231029104358341

我找到了这个,这个貌似也是 dlink 的技术支持网站,但是比上面那个网站多了个后缀 au

下载链接:http://support.dlink.com.au/download/download.aspx?product=DIR-825

image-20231029104521250

不过华神又给我了一个更全、更方便的网站 https://ftp.dlink.ru/pub/Router/ ,这里找 D-Link 的固件应该是很方便了。

自主探索

需要注意的是上面固件下载的过程中寻找的是 DIR-825 ,但后面的所有分析都是基于固件 DIR-850 。经过简单的分析,我发现了一处疑似命令注入漏洞和一处疑似栈溢出漏洞的地方

疑似的命令注入?

位于 soapcgi_main 函数中,这里 REQUEST_URI 内容给到了 v3 后续判断是否有 ?service= ,做了简单的过滤后最终可以命令执行

image-20231108180343514

疑似的栈溢出?

位于 sessioncgi_main 函数这里 REQUEST_URI 无论匹配到的是 form_login 还是 form_logout 都可以触发 weblogin_log 函数

image-20231108180318418

可以发现 weblogin_log 函数的第二个参数(也就是 getenv("REMOTE_ADDR") 的值 )与 ::ffff: 做对比,如果检查通过则执行 strcpy 函数进行拷贝。如果 REMOTE_ADDR 的值可控,在拷贝时就会造成栈溢出

image-20231108182521717

验证

因为我感觉这两个漏洞都很浅,这种很多年前的设备都被无数人光临过了,大概率已经有了漏洞相关信息或者压根就用不了,但我还是想独立的确定一下漏洞到底能否利用。

直接访问 soap.cgi ,返回了 404 (这里我不是很懂,但也有一点眉目,具体记录在了文章末尾 ),这意味着无法触发 soapcgi_main 函数

image-20231114082744570

这篇 文章 中提到,但是经过真机测试,发现设备并没有开启对 soap 的支持(我不确定能否手动开启它,我尝试了一些已知的方法,均未成功)

如果我们将我们的注入指令放入service参数,即可完成指令注入,现在我们还需要知道这个漏洞如何触发,从soapcgi_main函数名可以得知这个函数是用于处理soap请求的,这是一种简单的基于XML的协议,可以使应用程序在分散或分布式的环境中通过HTTP来交换信息,也就是说首先目标设备要开启对soap的支持此漏洞才可以触发。

去测试 sessioncgi_main 函数,我先用 qemu 用户级仿真了一下,发现如果 REMOTE_ADDR 确实可控的话,在漏洞函数返回时的地址会被破坏,导致程序崩溃。

image-20231108192425522

咸鱼上淘了一个二手的 DIR-850L 真机,我打了一下发现响应包是正常的,返回了 ok

image-20231108193051156

返回 ok 就意味着下图中的 printf 已经正常执行了,这说明 weblogin_log 函数正常返回,并没有栈溢出导致崩溃。

image-20231108193255552

于是乎开始各种排查,我开始的重心一直在排查报文编写错误。REMOTE_ADDR 的值没有添加双引号?GET 方式试一下?溢出的字节不够多? 还有什么字段也要伪造,没注意到?还是已经崩溃了,只不过WEB 界面没看到效果?

反正我当时抛出了很多疑问,然后一一进行了排除和验证,最后猜测只可能是 REMOTE_ADDR 字段没有控制成功。

经过搜索后,这篇 文章 里提到 REMOTE_ADDR 字段是无法被客户端控制,尽管我感觉这里还是很奇怪,因为我用 wireshark 抓了一下发送的流量包,看到的 REMOTE_ADDR 还是我控制的那个值🙃

image-20231113111829911

未初始化漏洞?

除了上面提到的两个地方,我还发现了一个奇怪的点。在 captchacgi_main 函数中,貌似 system 执行时可能会存在一个未初始化漏洞?观察下图发现 v10 ,在执行 sprintf 函数进行了拼接然后赋值给了 v11,可是 v10 自始至终并没有被赋值。我考虑有没有机会用栈里的残留值控制 v10 ,于是又有了后文的探索

image-20231114093006176

运行程序,发现一直卡住了,并且没有任何回显,说明大概率陷入了某个死循环或者超长循环里。

image-20231114093702458

分金定穴

为了解决这个问题,我采用的方法是下大量断点来缩小范围,确定到底卡在了哪个位置(我自称这种方法为 分金定穴 😆)第一次将断点下到了 cgibin_parse_request 函数执行前

image-20231113171937913

打完断点,发现可以直接 c 过来,排除了在此之前卡住的可能

image-20231113172012592

第二次将断点打到红框中的位置 sess_generate_captcha 函数执行前

image-20231113172222916

发现依然可以 c 过来,第三次将断点打到 0x40A3E0 地址处(也就是 sess_generate_captcha 函数执行后),此时发现 c 的时候,就一直卡住了。因此判断大致范围一定是在 sess_generate_captcha 函数里

image-20231113172422626

于是重调第二次,直接下到 sess_generate_captcha 函数内部,我选择下到 sub_4095B4 函数之前

image-20231113172727610

此时发现依然可以正常 c 过来,第二次我尝试下到了 sub_4095B4 函数执行之后

image-20231113173009502

发现又卡住了,那就说明具体的范围就在 sub_4095B4 函数里被什么东西卡住了

image-20231113173138521

直接分析 IDA 中的代码,发现 v4 没有初始化,而 v0 初始值是 1,每次循环 +1 ,只有 v1 的值大于 v4 时才能跳出这个死循环。

image-20231113173503142

因为没有变量初始化,就导致从栈里取的值是随机的,所以再次调试,看下 v4 的初始值是什么,找到 slt 比较的汇编代码, $v0 寄存器就为 v4 的值,将断点下到这里

image-20231113174105614

通过调试发现 $v0 寄存器中是一个栈地址,这意味着要跑二十多亿次的循环才能继续往下运行

image-20231113174442476

但这是否意味着真实环境里的这个 captcha.cgi 也要等这么久?拿真机测试一下,发现直接回显,这意味着远程环境里的这个 v4 肯定不是栈地址,我猜测大概率可能就是个 0

image-20231113175346616

因此为了和真实环境保持尽可能的接近,我选择用 set 命令直接把 $v0 寄存器改成 0 ,这样就可以正常执行到下面的 if 分支,但因为某些条件,导致进入的是 else

image-20231113175922743

访问真机可以看到触发的是 if 里的内容,依然是为了和真实环境保持一致,所以继续用 set 命令来改变正常的执行流

image-20231113180322427

此时执行到了 sprintf 函数,查看第四个参数(结合下面两张图)发现其是个栈里残留的地址,在其低地址处没有输入大量字符串的机会,所以此处 system 传入的一个未初始化数组也不可控制(本想着用栈的残留值偷鸡)

image-20231113181232436 image-20231113180808173

😅又是一次失败的分析

问题与解决

这里对探索过程中遇到的问题和解决方法进行了记录

gdbserver连接报错

(gdb) target remote 192.168.110.111:7788
Remote debugging using 192.168.110.111:7788
1
sl
Ignoring packet error, continuing...
warning: unrecognized item "timeout" in "qSupported" response
si



Ignoring packet error, continuing...
Ignoring packet error, continuing...
Bogus trace status reply from target: timeout

解决方法:将启动 qemu 时的 vmlinux-2.6.32-5-4kc-malta 改成 vmlinux-3.2.0-4-4kc-malta

gdb-multiarch xxx 发生段错误

image-20231101103629341

出现上面的问题,不能写成 gdb-multiarch session.cgi ,因为本地加载 session.cgi 的时候会缺少对应的库,触发段错误。直接写成 gdb-multiarch 就行

gdb-multiarch报错gdb.MemoryError

image-20231101103553908

这个问题说明 gdb 没能正确的解析接收的数据,需要提前设置架构和字节序。假如我这个要调试的程序为 mips 架构,大端序。执行 set endian big set architecture mips 两个命令即可

调试二进制文件报错 ld-uClibc.so.0: No such file or directory

开始直接在 Desktop 目录执行了 sudo chroot . ./qemu-mips-static ./captcha.cgi 命令,但是报错如下

image-20231107102050040

这里是因为 chroot 做了一个隔离环境,以 Desktop 目录作为根目录,而 captcha.cgi(已经过重命名,源程序名为 cgibin)为动态链接程序,会去寻找 /lib 目录下的 libcld 文件,因为我这里的 Desktop 目录下肯定没有 lib 目录,导致报错。

解决方法:进入由路由器固件解压后的文件系统,执行上面相同的命令,以当前目录为根目录做一个隔离环境,这里的 /lib 肯定是存在与 cgibin 配套的 libc 文件(如下)

image-20231113164300933

为什么有的cgi可以被触发,有的则404

我原先也是一直纳闷这个问题的,直到我无意中搜了一下文件系统里的 cgi 文件,我突然感觉这些文件很眼熟。

image-20231114123911932

因为我在真机上测试了一下 cgibin 程序中的哪些名称是可以访问成功的,貌似那些名称就是上面的文件。仔细比对后,确实如此。只要是在 htdocs/web 目录下的文件都可以直接被访问到,而其他文件之所以 404 , 就是因为 web 目录下并没有对应的 cgi 文件?但返回 500 的又会因为什么原因呢,暂且未知。

参考文章

c++ - 通过gdb连接到远程gdbserver时出错 - IT工具网 (coder.work)

D-Link DIR-825和TRENDnet TEW-632BRP命令注入漏洞(CVE-2020-10216) - 知乎 (zhihu.com)

Hacking the dlink DIR-615 for fun and no profit Part 5: Multiple RCE’s | by Brandon Roldan | Medium — 为了好玩而无利可救地破解 dlink DIR-615 第 5 部分:多个 RCE |由 Brandon Roldan |中等

D-Link DIR850L-A1固件指令执行漏洞_ghidra逆向dlink固件-CSDN博客