该系列设备已经进入 EOL (End Of Life) 阶段,官方不会针对漏洞发布安全更新。
建议用户及时下线并替换该系列设备。
CVE-2020-3144
基本信息
Cisco 官方发布信息: https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-rv-auth-bypass-cGv9EruZ
漏洞评分:9.8(Critical)
漏洞描述:此漏洞位于 web 管理界面中,影响 Cisco RV110W,RV130 以及 RV215W 等设备。由于对 session 的管理存在问题,攻击者可以构造特殊格式的 HTTP 请求触发漏洞,成功的攻击可以使攻击者获取设备的管理员权限。
漏洞分析
此漏洞在 1.2.2.8 版本中进行了修复。
固件下载链接(请下载 1.2.2.8 以下版本):https://software.cisco.com/download/home/283879340/type/282487380/release/1.2.2.8?catid=268437899
binwalk 可以顺利的解压固件,得到一个标准的嵌入式 Linux 文件系统。
漏洞在处理 http 请求的文件中,文件路径:/usr/sbin/httpd。它是一个 MIPS32 小端序的二进制文件,直接使用 Ghidra 打开。
关键函数位于 log_in_cgi 中,此函数主要实现用户登录的逻辑,函数代码稍长,关键部分如下
1 | enc = strcmp(__s1_00,"continue"); |
__s1_00 变量由 get_cgi 函数返回
1 | method = (char *)get_cgi("submit_type"); |
经过抓包发现 get_cgi 函数用于获取用户发送请求中的字段,submit_type 是用户提交请求的类型,登录的时候主要有两种类型,一是 set_lang,用于设置语言,二是 continue,这是我们需要关注的请求。
那么 continue 是什么功能呢?如果有设备的话就很好验证了,打开 web 管理界面如下
此时可以正常登录管理员账户(默认用户名密码为 cisco:cisco),成功登录后如果没有主动登出,只是简单的关闭了网页,后台的管理员账户是不会自动登出的,此时再发起一个新的登录请求,会看到如下界面
开启抓包,点击 continue,得到请求如下
1 | POST /login.cgi HTTP/1.1 |
此时 submit_type 就是 continue,根据代码来看,当用户发送了 continue 请求的时候,并且当前 nvram 中的 session_key 字段不为空,就直接设置登录信息,不验证用户名和密码,并且这个请求不需要身份验证。
所以如果我们能在真正的管理员发起登录请求和 continue 请求之间发送 continue 请求,就可以截获管理员的 session_id,从而获取后台管理权限。
此漏洞的 POC 如下
1 | # Code from https://quentinkaiser.be/exploitdev/2020/07/14/breaking-cisco-rv-again/ |
注:session_id 和登录者的 IP 具有对应关系,例如管理员从 ip 为 192.168.1.100 的机器上登录了路由器后台,攻击者从 ip 为 192.168.1.101 的机器上进行 session_id 劫持得到 session_id,但由于 ip 的绑定关系,他并不能直接从本机登录。
CVE-2020-3145 & CVE-2020-3146 & CVE-xxxx-xxxx
基本信息
CVE-2020-3145 & CVE-2020-3146 两个漏洞官方信息: https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-rv-rce-m4FEEGWX
由于这些漏洞都位于后台,实际利用价值不大,所以我们以一个为例分析。
3145 和 3146 两个漏洞的分析可以在这里找到(感谢作者),我们要分析的是一个此前没有被披露的漏洞,并且在最新版中也存在(1.2.2.8)。
漏洞分析
这些后台漏洞成因基本相同,都是对用户提交的数据验证不严格导致的。我们分析的漏洞位于函数 save_http_user (1.2.2.8),Ghidra 可以直接进行反编译,结果如下
1 |
|
这个功能是用于开启路由器的访客账户,开启之后访客也可以登录路由器进行一定的管理,有利于管理角色的分割。
第 56 行之前的代码多次使用 get_cgi 函数获取用户提交请求中的数据,第 58 行调用了 setpwd 函数设置访客用户的密码,其中第二个参数是 guest_name,即用户提交的访客用户名。
setpwd 函数的代码如下
1 |
|
可以很明显的看到第 53 行使用 sprintf 函数将外部传入的参数拷贝到栈变量,其中最后一个参数就是我们关注的 guest_name 变量。(第五和第六个参数也可以导致溢出,不过选择最后一个参数可以减少干扰)
如果传入一个超长的字符串,则可以触发栈溢出,结合 ROP 等技术可以实现任意代码执行。(攻击分析见下文)
另外,在函数 validate_guest_vlan 中也存在类似的问题,不过这些漏洞都属于后台漏洞,实际攻击中价值不是很大。
CVE-2020-3323
基本信息
官方发布信息: https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-rv-rce-m4FEEGWX
漏洞评分:9.8(Critical)
漏洞描述:此漏洞位于 web 管理界面中,影响 Cisco RV110W,RV130 以及 RV215W 等设备。未经授权的攻击者可以通过构造特殊格式的 HTTP 请求,触发此漏洞,从而导致未授权的任意代码执行。
漏洞分析
此漏洞在 1.2.2.8 版本中进行了修复。
2020 年强网杯的 IoT 项目攻击目标之一就是 RV110W,现场应该有队伍利用了这个 nDay 完成了攻击。
此漏洞位于函数 guest_logout_cgi 中,Ghidra 反编译结果如下
1 |
|
第 52 行使用 get_cgi 函数获取 submit_button 字段的内容,第 80 行使用 strstr 判断其中是否存在字符串 “status_guestnet.asp”,如果存在,则执行第 99 行的代码,使用 sscanf 将数据拷贝到两个栈变量中。
很明显这里存在栈溢出漏洞,如果用户构造一个包含 “status_guestnet.asp” 的超长字符串,就可以触发栈溢出漏洞,进而结合 ROP 等技术实现任意代码执行。(攻击分析见下文)
经过测试,这个函数对应的后台接口是 guest_logout.cgi,并且不需要身份验证,所以我们可以构造下面的数据包触发此漏洞。
1 | POST /guest_logout.cgi HTTP/1.1 |
注:在第 38 行尝试获取 cip 字段的内容,如果我们不提供这个字段,将返回 0,经验证会在 strlen 函数中导致空指针解引用,可以使 httpd 服务崩溃。
CVE-2021-34730
基本信息
漏洞评分:9.8(Critical)
漏洞描述:A vulnerability in the Universal Plug-and-Play (UPnP) service of Cisco Small Business RV110W, RV130, RV130W, and RV215W Routers could allow an unauthenticated, remote attacker to execute arbitrary code or cause an affected device to restart unexpectedly, resulting in a denial of service (DoS) condition.。
漏洞分析
此漏洞存在于解析 SSDP 协议的过程,位于函数 m_search_handler,代码如下
1 | undefined4 m_search_handler(int upnp_context) |
第 30 行代码从 upnp_context 结构体中获取了某个数据,通过对下面流程的分析可以确定这里获取的是 ST 字段,第 78 行代码引用了 strcpy 函数将内容直接拷贝到栈变量中,如果攻击者传入一个超长的字符串,将导致栈溢出。(攻击分析见下文)
漏洞利用分析
在具体分析之前,先设置好分析环境,拆开路由器之后在右上角可以找到 UART 串口,并且标记了接口名称,只需要焊接好排针就可以使用了。
接入 FT232 之后在电脑打开串口软件,设备启动完毕按回车拿到 shell
先上传一个 gdbserver 方便后续调试,设备默认带了 wget 命令,我们可以在本机开启一个 Server 放入 gdbserver,然后用 wget 下载到路由器。
gdbserver 下载链接:链接:https://pan.baidu.com/s/1e6-lxtzI0aXNe0aGfiWRCg 提取码:yyln
wget 命令
1 | wget http://192.168.1.101:8000/gdbserver.mipsle |
CVE-2020-3323
经过上面的环境配置,我们现在可以对 httpd 服务进行调试,先在路由器上面找到 httpd 进程 pid
1 | ps | grep http |
这里会返回两个结果
1 | # ps | grep http |
其中 httpd -S 是我们的调试目标。启动 gdbserver 进行附加
1 | ./gdbserver.mipsle 192.168.1.1:12345 --attach 356 |
成功附加之后在本机启动 gdb-multiarch,执行下面的命令
1 | set sysroot ./ |
成功启动调试环境,可以先编写一个 POC 脚本,方便后续操作,之前分析了漏洞成因,可编写出下面的 POC 脚本
1 | import requests |
使用方法:python poc.py -ip 192.168.1.1
发送 payload 之后 gdb 中看到崩溃结果
成功控制 PC,接下来就尝试利用漏洞。
关于 MIPS 架构的漏洞利用文章网络上有很多,这里就不详细介绍了,由于整个系统没有开启 aslr,libc 地址固定,所以我们的思路就是构造 ROP 直接执行 system 函数,参数中插入开启 telnetd 的命令。
首先用 vmmap 命令获取设备的 libc 地址:
1 | pwndbg> vmmap |
可以看到 libc 加载到 0x2af98000 这个地址,并且每次重启都不会改变。
ROP 思路是调用 system(“utelnetd -d -l /bin/sh -p 1337 &”),有了 libc 地址接下来需要寻找 system 函数的地址,目标 libc 文件位于 /lib/libc.so.0,将它加载到 IDA 可以找到 system 函数的偏移量为 0x0004C7E0。
找到 system 函数地址,还需要解决参数的问题,MIPS 中函数的前几个参数由 a0、a1 等寄存器进行传递,而由于可控的内存只在栈上,所以肯定要想办法把栈地址加载到这些寄存器中。
由于 MIPS 架构的特性,确实存在这类指令可以用于 ROP,在 mips rop finder 中使用 mipsrop.stackfinder() 就可以找到。
1 | Python>mipsrop.stackfinder() |
具体应该用哪条就要通过调试来确定了,我们希望执行 system 函数,所以要将栈地址加载到 a0 寄存器中,通过调试发现 0x000257A0 这条指令比较合适。另外还需要注意一点,这类 gadget 的转移条件并不是像 x86 一样通过栈中的返回地址返回,而是 jr <某个内存地址 or 寄存器>,我们选择的这条 gadget 转移条件是 jr $s0,也就是说,需要提前控制 s0 寄存器为下一个 gadget 的地址,才能保证 ROP 链顺利执行。
一般情况下 MIPS 函数返回的时候都会把栈中的部分数据转移到 s0 ~ s8 寄存器中,所以只要提前在栈中布置好 s0 对应的地址即可。
到这里必要的地址都找到了,payload 的构成应该是
1 | padding + next_gadget_address + padding2 + gadget_address + padding3 + command |
gadget_address 占据返回地址的位置,程序跑到这里先运行第一段 gadget,将栈地址加载到 a0,这个地址就指向 command,然后 jr $s0,返回到 next_gadget_address,也就是执行 system 函数,从而实现 system(command)。
CVE-2021-34730
upnp 这个也是栈溢出漏洞,利用思路和上一个非常类似,执行 system(command) 即可,不过 upnp 服务开放在 udp 端口,在编写脚本的时候可能需要注意一下。
后台栈溢出
漏洞利用思路和前面的相同,但是后台价值不高,就不演示了。
- 本文作者: CataLpa
- 本文链接: https://wzt.ac.cn/2020/11/10/cisco-rv110w-bugs/
-
版权声明:
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。