CDPwn系列之CVE-2020-3119分析

阅读量    125869 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

漏洞简介

CDPwn系列漏洞是由来自Armis的安全研究员在思科CDP(Cisco Discovery Protocol)协议中发现的5个0 day漏洞,影响的产品包括思科交换机、路由器、IP电话以及摄像机等。其中,CVE-2020-3119NX-OS系统中存在的一个栈溢出漏洞,利用该漏洞可在受影响的设备(如Nexus系列交换机)上实现任意代码执行,如修改Nexus交换机的配置以穿越VLAN等。

下面借助GNS3软件搭建Nexus交换机仿真环境,来对该漏洞进行分析。

 

环境准备

根据漏洞公告,选取Nexus 9000 Series Switches in standalone NX-OS mode作为分析目标,获取到对应的镜像如nxosv.9.2.2.qcow2后,根据GNS3提供的Cisco NX-OSv 9000 appliance中的模板进行操作即可。需要说明的是,

  • 与思科ASAV防火墙不同,模拟Nexus 9000系列交换机除了需要设备镜像外,还需要一个UEFI格式的启动文件;
  • 模拟Nexus 9000系列交换机对虚拟机的配置要求较高(8G内存),建议采用GNS3设备模板中的默认配置,降低配置的话可能导致设备无法启动。

设备启动后,建议连接到设备的Ethernet1/1口,之后对设备进行配置。

Nexus 9000系列交换机上,存在以下3种shell

  • vsh:正常配置设备时CLI界面的shell
  • guestshell:在vsh中运行guestshell命令后进入的shell,可以运行常见的shell命令;
  • bash shell:在vsh中运行run bash命令后进入的shell,可以查看底层系统中的文件,以及设备上的进程信息等;

    需要先在configure模式下,运行feature bash-shell开启bash shell

默认配置下,bash shell中是没有ip信息的。为了方便后续进行分析调试,需要给之前连接的Ethernet1/1口配置ip信息,根据mac地址查找对应的网口,然后配置对应的ip即可。

设备的mgmt口在bash shell下不存在对应的网口

另外,由于采用binwalk工具对设备镜像进行解压提取失败,因而直接通过bash shell拷贝设备文件系统中的文件:将公钥置于/root/.ssh/authorized_keys,然后通过scp方式进行拷贝即可。

 

CDP数据包分析

为了便于后续的分析,需要先了解CDP数据包的相关格式。在GNS3中设备之间的链路上捕获流量,看到设备发送的CDP数据包示例如下。

可以看到,除了开始的versionttlchecksum字段外,后面的每一部分都是典型的TLV(Type-Length-Value)格式,Device IDAddresses部分的字段明细如下。其中,在Addresses部分,其Value还有更细致的格式。

另外,python scapy模块支持CDP协议,可以很方便地构造和发送CDP数据包,示例如下。

from scapy.contrib import cdp
from scapy.all import Dot3, LLC, SNAP, sendp

ethernet = Dot3(dst="01:00:0c:cc:cc:cc")
llc = LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/SNAP()
# Cisco Discovery Protocol
cdp_header = cdp.CDPv2_HDR(vers=2, ttl=180)
deviceid = cdp.CDPMsgDeviceID(val='nxos922(97RROM91ST3)')
portid = cdp.CDPMsgPortID(iface="br0")
address = cdp.CDPMsgAddr(naddr=1, addr=cdp.CDPAddrRecordIPv4(addr="192.168.110.130"))
cap = cdp.CDPMsgCapabilities(cap=1)
power_req = cdp.CDPMsgUnknown19(val="aaaa"+"bbbb")
power_level = cdp.CDPMsgPower(power=16)
cdp_packet = cdp_header/deviceid/portid/address/cap/power_req/power_level

sendp(ethernet/llc/cdp_packet, iface="ens36")

 

漏洞分析

根据Armis技术报告可知,该漏洞存在于程序/isan/bin/cdpd中的函数cdpd_poe_handle_pwr_tlvs()里,其主要功能是对Power Request(type=0x19)部分的数据进行解析和处理,该部分的协议格式示例如下。

函数cdpd_poe_handle_pwr_tlvs()的部分伪代码如下,其中,cdp_payload_pwr_req_ptr指向Power Request(type=0x19)部分的起始处。可以看到,首先在(1)处获取到Length字段的值,在(2)处计算得到Power Requested字段的个数(Type + Length + Request-ID + Management-ID为8字节,Power Requested字段每项为4字节),之后在(4)处将每个Power Requested字段的值保存到v35指向的内存空间中。由于v35指向的内存区域为栈(在(3)处获取局部变量的地址,其距离ebp的大小为0x40),而循环的次数外部可控,因此当Power Requested字段的个数超过0x11后,将覆盖栈上的返回地址。

// 为方便理解, 对函数/变量进行了重命名
char __cdecl cdpd_poe_handle_pwr_tlvs(int *a1, int cdp_payload_pwr_cons_ptr, _WORD *cdp_payload_pwr_req_ptr)
{
  v32 = *a1;
  result = cdp_payload_pwr_cons_ptr == 0;
  v33 = cdp_payload_pwr_req_ptr == 0;
  if ( cdp_payload_pwr_cons_ptr || !v33 )
  {
    v28 = a1 + 300;
    if ( v33 && cdp_payload_pwr_cons_ptr )  // version 1
    {
      // ...
    }
    if ( !result && !v33 )  // version 2
    {
      // ... 
      cdp_payload_pwr_req_len_field = __ROR2__(cdp_payload_pwr_req_ptr[1], 8);  // (1)
      cdp_payload_pwr_req_req_id = __ROR2__(cdp_payload_pwr_req_ptr[2], 8);
      cdp_payload_pwr_req_mgmt_id = __ROR2__(cdp_payload_pwr_req_ptr[3], 8);
      // ...
      v8 = cdp_payload_pwr_req_len_field - 8;
      if ( v8 < 0 )
        v8 = cdp_payload_pwr_req_len_field - 5; 
      cdp_payload_pwr_req_num_of_level = (unsigned int)v8 >> 2;  // (2)
      // ...
      if ( (signed int)cdp_payload_pwr_req_num_of_level > 0 )
      {
        cdp_payload_pwr_req_level_ptr = (unsigned int *)(cdp_payload_pwr_req_ptr + 4);
        v35 = &cdp_payload_pwr_cons_len_field;  // (3) cdp_payload_pwr_cons_len_field: [ebp-0x40]
        pwr_levels_count = 0;
        do
        {
          *v35 = _byteswap_ulong(*cdp_payload_pwr_req_level_ptr);  // (4)
          // ...
          a1[pwr_levels_count + 311] = *v35;  // (5) 
          ++cdp_payload_pwr_req_level_ptr;
          ++pwr_levels_count;
          ++v35;
        }
        while ( cdp_payload_pwr_req_num_of_level > pwr_levels_count );    // controllable
      }
      v9 = *((_WORD *)a1 + 604);
      v10 = *((_WORD *)a1 + 602);
      v11 = a1[303];
      if ( cdp_payload_pwr_req_req_id != v9 || cdp_payload_pwr_req_mgmt_id != v10 ) // (6)
      {
        // ...
}

在后续进行漏洞利用时,由于在(5)处将v35指向的内存地址空间的内容保存到了a1[pwr_levels_count + 311]中,而该地址与函数cdpd_poe_handle_pwr_tlvs()的第一个参数有关,在覆盖栈上的返回地址之后也会覆盖该参数,因此需要构造一个合适的参数,使得(5)处不会崩溃。另外,还要保证(6)处的条件不成立,即执行else分支,否则在该函数返回前还会出现其他崩溃。

另外,cdpd程序启用的保护机制如下,同时设备上的ASLR等级为2。由于cdpd程序崩溃后会重启,因此需要通过爆破的方式来猜测程序相关的基地址。

$ checksec --file cdpd
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RPATH:    b'/isan/lib/convert:/isan/lib:/isanboot/lib'

之后漏洞利用可以执行注入的shellcode,或者通过调用system()来执行自定义的shell命令。

通过/isan/bin/vsh可以执行设备配置界面中的命令,如system('/isan/bin/vsh -c "conf t ; username aaa password test123! role network-admin"执行成功后,会添加一个aaa的管理用户。

 

补丁分析

根据思科的漏洞公告,该漏洞在如下的版本中已修复。

7.0(3)I7(8)为例,函数cdpd_poe_handle_pwr_tlvs()的部分伪代码如下。可以看到,在(1)处增加了对Power Requested字段个数的判断,其最大值为10。

char __cdecl cdpd_poe_handle_pwr_tlvs(int *a1, int a2, _WORD *a3)
{
  // ...
  if ( !result && !v35 )  // version 2
  {
    // ...
    v38 = __ROR2__(a3[1], 8);
    v33 = __ROR2__(a3[2], 8);
    v32 = __ROR2__(a3[3], 8);
    // ...
    v8 = v38 - 8;
    if ( v8 < 0 )
      v8 = v38 - 5;
    v29 = (unsigned int)v8 >> 2;
    if ( v29 <= 0xAu )  // (1)
    {
      // ...
    }
    else
    {
      // ...
      v36 = 10;  // (2)
      v28 = 10;
      v29 = 10;
    }
    v39 = 0;
    do
    {
      v9 = *v37;
      LOWORD(v9) = __ROR2__(*v37, 8);
      v9 = __ROR4__(v9, 16);
      LOWORD(v9) = __ROR2__(v9, 8);
      v42[v39] = v9;
      // ...
      a1[v39 + 312] = v42[v39];
      ++v37;
      ++v39;
    }
    while ( v36 > v39 );
    goto LABEL_78;
  }
  // ...
}

 

小结

  • 通过GNS3软件搭建设备的仿真环境,同时对该漏洞的形成原因进行了分析:在对Power Request(type=0x19)部分的数据进行解析时,由于缺乏对其内容长度的校验,造成栈溢出。

 

相关链接

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多