0x00 漏洞背景

2020年6月16日,360工业及车联网安全事业部 监测发现 Treck Inc. 官方发布了针对 Treck TCP/IP协议栈 中共19个漏洞的安全通告。其中有4个漏洞CVSS评分高于9分。360工业及车联网安全事业部 在第一时间,针对该漏洞进行了深入分析。

Treck Inc. 所开发的网络协议栈包括底层的TCP/IP协议,以及各种应用层网络协议。Treck的网络协议栈被广泛的用于各类实时嵌入式系统以及工控设备中。

本次公布的漏洞中,有两个漏洞存在远程代码执行的可能。其中 CVE-2020-11896 是存在于IP层解析模块中的漏洞,攻击者可以通过发送精心构造的UDP报文触发Treck协议栈中IP层的分片重组功能,造成远程代码执行。而 CVE-2020-11901 是存在于DNS解析模块中的漏洞,该漏洞可通过回复设备发出的单条DNS请求报文触发,从而造成远程代码执行。

全部公开的19个漏洞已经在 6.0.1.67 及更高的版本中得到了修复,厂商也提出了针对不同场景下的漏洞缓解措施。360工业及车联网安全事业部 建议相关用户,根据实际情况采取适合的方法对漏洞进行修复。

0x01 风险等级

360工业及车联网安全事业部针对Ripple20中19个漏洞进行了风险评估,详情见下表:

CVE ID 漏洞描述 威胁等级 影响面
CVE-2020-11896 IPv4 / UDP组件中对长度参数不一致的处理不当,可能导致远程代码执行。 严重 广泛
CVE-2020-11897 IPv6组件中对于长度参数不一致的处理不当,可能导致越界写入。 严重 广泛
CVE-2020-11898 IPv4 / ICMPv4组件中对长度参数不一致的处理不当,可能导致敏感信息泄露。 严重 一般
CVE-2020-11899 IPv6组件中的输入验证不正确,可能导致越界读取/拒绝服务。 中危 一般
CVE-2020-11900 IPv4隧道组件中可能存在双重释放,可能导致释放后使用。 高危 一般
CVE-2020-11901 DNS解析器组件中存在输入验证不正确。可能导致远程代码执行。 严重 广泛
CVE-2020-11902 IPv6OverIPv4隧道组件中的输入验证不正确,可能造成越界读取。 高危 一般
CVE-2020-11903 DHCP组件中存在越界读取。可能导致敏感信息泄露。 中危 一般
CVE-2020-11904 内存分配组件中存在整数溢出,可能造成越界写入。 高危 一般
CVE-2020-11905 DHCPv6组件中存在越界读取问题,可能造成越界读取。 中危 一般
CVE-2020-11906 以太网链路层组件中存在输入验证不正确,可能造成整数溢出。 中危 一般
CVE-2020-11907 TCP组件中对参数长度不一致的处理不当,可能造成整数溢出。 中危 一般
CVE-2020-11908 DHCP组件中的Null Termination不正确,可能导致敏感信息泄露。 中危 一般
CVE-2020-11909 IPv4组件中的输入验证不正确,可能导致整数溢出。 中危 一般
CVE-2020-11910 ICMPv4组件中的输入验证不正确,可能导致越界读取。 中危 一般
CVE-2020-11911 ICMPv4组件中的访问控制不正确 中危 一般
CVE-2020-11912 TCP组件中的输入验证不正确,可能导致越界读取。 中危 一般
CVE-2020-11913 IPv6组件中的输入验证不正确,可能导致越界读取。 中危 一般
CVE-2020-11914 ARP组件中的输入验证不正确,可能导致越界读取。 中危 一般

0x02 漏洞分析

前置知识

在针对CVSS评分10分的CVE-2020-11896的漏洞进行分析之前,首先需要简单的了解一下两个IP协议中的概念--IP分片与IP隧道。

IP分片

由于不同的网络环境对于IP报文的长度限制不同。所以当转发长度大于该网络允许的最大长度的报文时,就需要对该报文进行IP分片,将原始的IP报文分成多个符合长度要求的短IP报文。而这些报文在到达目的地后会通过IP协议进行重组,恢复成原始的IP报文。

IP隧道

IP隧道允许在两个单独的网络之间进行虚拟的点对点链接。 通过将一个数据包(可能是IP数据包)封装在另一个数据包中,可以使内部数据包具有与外部数据包不同的源地址和目标地址。(VPN就是一种IP隧道)。而触发此漏洞需要使用一种最简单的IP隧道 IP-in-IP。IP-in-IP协议在IP协议层外侧再封装一层IP协议,以达到进行虚拟点对点链接的目的。

tunnelling

代码分析

Treck的协议栈使用结构体tsPacket来存储所有与数据包的基本信息

struct tsPacket {
    ttUserPacket pktUserStruct;
    ttSharedDataPtr pktSharedDataPtr; // Point to corresponding sharable ttSharedData
    struct tsPacket * pktChainNextPtr; // Next packet (head of a new datagram in a queue)
    struct tsDeviceEntry * pktDeviceEntryPtr; // pointer to network Device struct
    union anon_union_for_pktPtrUnion pktPtrUnion;
    tt32Bit pktTcpXmitTime;
    tt16Bit pktUserFlags;
    tt16Bit pktFlags;
    tt16Bit pktFlags2;
    tt16Bit pktMhomeIndex;
    tt8Bit pktTunnelCount; // Number of times this packet has been decapsulated. Initially set to zero.
    tt8Bit pktIpHdrLen; // Number of bytes occupied by the IP header.
    tt8Bit pktNetworkLayer; // Specifies the network layer type of this packet (IPv4, IPv6, ARP, etc).
    tt8Bit pktFiller[1];
};

ttUserPacket结构体中保存了数据包数据相关的数据。

struct tsUserPacket {
    void * pktuLinkNextPtr; // Next tsUserPacket for fragmented data
    ttUser8BitPtr pktuLinkDataPtr; // Pointer to data
    ttPktLen pktuLinkDataLength; // Size of data pointed by pktuLinkDataPtr
    ttPktLen pktuChainDataLength; // Total packet length (of chained fragmented data). Valid in first link only.
    int pktuLinkExtraCount; // Number of links linked to this one (not including this one). Valid in first link only.
};
  • pktuLinkNextPtr:Treck协议栈在处理分片的情况时,使用链表的结构存储分片的信息。一个tsUserPacket就对应一个分片,而 pktuLinkNextPtr指针就是指向的是下一个分片,当后面没有分片更多分片时(包括不使用分片,即只有一个分片时),pktuLinkNextPtr指针设为Null。
  • pktuLinkDataPtr:指向数据存储的地址。
  • pktuLinkDataLength:本分片中所接收到数据的长度。
  • pktuChainDataLength:整个链表中所有接收到的数据长度(只有链表中第一个节点的值是有效的)。

link

当数据在不同层级的协议中传递时,如将以太网帧传递到IP层时,Treck协议栈会修改tsUserPacket结构体中的数据,该结构体就从以太网帧变为了IP报文。

pkt->pktuLinkDataPtr = pkt->pktuLinkDataPtr + 0xe;
pkt->pktuLinkDataLength = pkt->pktuLinkDataLength - 0xe;
pkt->pktuChainDataLength = pkt->pktuChainDataLength - 0xe;

传入的数据报文由具有相同命名约定tf*IncomingPacket的函数处理(据我们所知),其中*是协议名称。 对于以太网/ IPv4,数据包将由函数tfEtherRecv,tfIpIncomingPacket处理。
Treck堆栈在函数tfIpReassemblePacket中处理IP分片的重组,该方法是从tfIpIncomingPacket调用的。每当接收到发往设备的IP分片时,就会调用此函数。如果缺少分片,函数将返回NULL。当所有分片都已经收到并且没有连续没有缺失(通过More Fragements和Fragment Offset确定),则协议栈将使用pktuLinkNextPtr字段将所有的分片链接在一起,并将数据包传递给下一层进行进一步处理。 进行IP分片重组的过程中,并不意味着将数据包复制到连续的存储块,而是简单地将它们在链表中链接在一起。

Root Cause分析

在函数tfIpIncomingPacket中,Treck协议栈在处理头部报文与实际接收到的长度不同时,使用了一种比较简单的处理方式。

if ((uint)ipTotalLength <= pkt->pktuChainDataLength) {
    if ((uint)ipTotalLength != pkt->pktuChainDataLength) {
        pkt->pktuChainDataLength = (uint)ipTotalLength;
        pkt->pktuLinkDataLength = (uint)ipTotalLength;
    }
    ...
}
  • ipTotalLength是IP头部中两字节的TotalLength字段
  • pktuChainDataLength是实际收到的IP层数据长度

当tfIpIncomingPacket接收到的IP报文中,TotalLength小于实际接收到的数据时。Treck协议栈选择接受IP头部中TotalLength所声明的长度,直接丢弃掉接收到的多余数据。将pktuChainDataLength和pktuLinkDataLength同时赋值为TotalLength。这样简单的处理方式在解析常规IP报文时看起来并不会产生什么异常,不论是否使用了分片的功能。但是当使用IP-in-IP协议时,这样简单的处理则会造成内存破坏。

但是当Treck协议栈在处理精心构造的IP-in-IP数据包时,则会造成堆溢出。数据包的构造如下图所示:

example

IP层:包含了两个分片。第一个分片包含了40byte的数据,其中包括上层协议(IP-in-IP、UDP)的包头,以及12字节的上层协议的数据;第二个分片中的包含了988字节,全部的都是上层协议的数据。
IP-in-IP层:未开启IP分片(MF=0),头部中TotalLength为32(此字段造成pktuChainDataLength与TotalLength的不同以触发漏洞)
UDP层:length字段设为为12(为了进入可以造成堆溢出的逻辑分支)

Treck协议栈在处理上述构造的报文时,则会产生堆溢出,具体流程如下:

  1. tfIpIncomingPacket接收外层IP分片,并交给tfIpReassemblePacket函数处理分片重组

  2. tfIpReassemblePacket函数在接收到全部外层IP分片后,交给协议栈解析上层协议

  3. 协议栈解析出上层为IP-in-IP协议后,对pk进行如代码所示的操作,然后再次将结构体交给tfIpIncomingPacket函数处理IP-in-IP(即IP)协议。

pkt->pktuLinkDataPtr = pkt->pktuLinkDataPtr + 20;
pkt->pktuLinkDataLength = pkt->pktuLinkDataLength - 20;
pkt->pktuChainDataLength = pkt->pktuChainDataLength - 20;
  1. tfIpIncomingPacket函数在处理报文时,由于TotalLength < pkt->pktuChainDataLength(32 < 1028)。所以将pkt->pktuLinkDataLength和pkt->pktuChainDataLength赋值为32。并交给协议栈继续处理内层报文。

  2. 协议栈解析出内层为UDP协议后,继续根据UDP包头的长度修改pkt的指针以及长度字段,交给tfUdpIncomingPacket处理UDP协议。

  3. tfUdpIncomingPacket在处理UDP数据时,会根据pkt->pktuChainDataLength的大小4(32-IP头部长度20-UDP头部长度8)使用tfGetSharedBuffer函数申请一块内存,并使用tfCopyPacket函数将全部的数据拷贝到所分配的内存中。而拷贝的方式就是通过pktuLinkDataPtr指针递归的将数据拷贝到所分配的缓冲区中。由于要拷贝的数据长度为1000字节,远大于缓冲区分配的大小。所以在数据拷贝的过程中造成了堆溢出。

if (uVar2 <= sizeOfPacketBuffer) {
    dst = tfGetSharedBuffer(0x54,pkt->pktuChainDataLength,0);
    if (dst != NULL) {
        tfCopyPacket(pkt,dst);
        needToDrop = true;
        local_10 = dst;
    }
}
i = 0;
do {
    memcpy(dst->pktuLinkDataPtr + i,src->pktuLinkDataPtr,src->pktuLinkDataLength);
    i = i + src->pktuLinkDataLength;
    src = (tsPacket *)src->pktuLinkNextPtr;
} while (src != NULL);

0x03 影响范围

Ripple20涉及到的厂商以及产品较多,部分已经得到了厂商的确认,而还有一部分还处于待定状态。全部涉及到的厂商在下图展示:

vendor

大部分确认的厂商已经在自己的官网中公布了涉及到Ripple20的产品,以及对应的解决措施。360工业及车联网安全事业部 收集了各厂商针对此次Ripple20漏洞的公告:

Aruba Networks

B|Braun USA

Baxter U.S.

Boston Scientific / Guidant Medical

CARESTREAM

CATERPILLAR

Cisco

Dell / EMC

EATON

DIGI

Elmic / KASAGO

Green Hills Software

HP

HPE

INTEL

KASAGO

McAfee

NetApp

ROCKWELL AUTOMATION

Schneider

Teradici

Treck

Xerox

0x04 修复方式及缓解措施

设备供应商的缓解措施

  • 确定是否使用易受攻击的Treck堆栈
  • 联系Treck了解风险
  • 更新到最新的Treck堆栈版本(6.0.1.67或更高版本)
  • 如果无法更新,请考虑禁用易受攻击的功能

相关设备用户

  • 联系设备供应商进行更新或漏洞缓解

网络管理者的缓解措施

  • 最好的缓解措施是将所有设备的修补程序版本更新。
  • 如果无法更新设备,建议执行以下步骤:
    • 将嵌入式和关键设备在网络中的暴露程度保持在最低水平;并关闭所有非必要的网络连接功能
    • 将操作技术网络和设备隔离在防火墙后,并将其与业务网络隔离
    • 仅启用安全的远程访问方法
  • 阻止异常IP流量
  • 通过深度数据包检查来阻止网络攻击,以减少对Treck嵌入式启用TCP / IP的设备的风险
  • 流量过滤是一种有效的技术,可以适当地应用于您的网络环境。过滤选项包括:
    • 规范化或阻止IP分片
    • 如果不需要,请禁用或阻止IP隧道(IPv6-in-IPv4或IP-in-IP隧道)
    • 禁止IP源路由以及以及所有不推荐使用的IPv6功能,例如路由头
    • 强制执行TCP检查,拒绝格式错误的TCP数据包
    • 阻止未使用的ICMP控制消息,例如MTU更新和地址掩码更新
    • 通过安全的递归服务器或DNS检查防火墙规范DNS
    • 提供DHCP / DHCPv6安全性,并具有DHCP监听等功能
    • 如果未在交换基础架构中使用,请禁用/阻止IPv6多播功能
    • 在可以使用静态IP的网络禁用DHCP
    • 使用网络IDS和IPS签名
    • 使用网络分段(如果可用)

0x05 参考链接

https://www.jsof-tech.com/ripple20

https://treck.com/vulnerability-response-information

https://nvd.nist.gov/vuln/detail/CVE-2020-11896

https://gist.github.com/SwitHak/5f20872748843a8ad697a75c658278fe