Skip to content

Treck公司TCP/IP漏洞曝光 _全球数亿物联网设备将会受到影响

2020年6月16日,位于以色列的一家专门从事物联网和嵌入式设备安全的JSOF公司警告说,由于严重安全漏洞影响了Treck TCP/IP协议堆栈,全球数亿台物联网设备可能会受到远程攻击。这一漏洞影响各行各业,危害波及家用设备、医疗保健、数据中心、企业、电信、石油、天然气、核能、交通运输以及许多其他关键基础架构。 今天,我们就来聊聊这个严重安全漏洞是什么,具体原理与实现,以及我们该尽如何应对。

一、什么是Treck TCP/IP漏洞?

Treck TCP/IP是一家总部位于美国辛辛那提的软件公司“Treck”在1997年推出的一套专用于嵌入式系统的TCP/IP 底层 Internet 协议套件库。通过设计针对内核、定时器、驱动程序、套接字的API接口,Treck TCP/IP 协议套件库可以很容易地集成到各种各样的嵌入式产品中。二十多年来,全球很多公司一直在使用这个库,使他们的设备或软件通过TCP/IP协议连接到互联网。 JSOF公司研究人员发现,在Treck TCP/IP协议栈6.0.1.66之前的版本中存在缓冲区错误漏洞。该漏洞源于网络系统或产品在内存上执行操作时,未正确验证数据边界,导致向关联的其他内存位置上执行了错误的读写操作。攻击者可利用该漏洞导致缓冲区溢出或堆溢出等。此次,JSOF公司研究人员发现该产品共有19个0day漏洞,影响了Treck公司网络协议的专有实现过程。由于供应链因素,这些漏洞的破坏性影响被放大,就像“涟漪效应”一样,又因为是在2020年报道出来的,所以将这一系列漏洞统称为“Ripple20”漏洞。

二、Treck TCP/IP漏洞意味着什么?

由于应用广泛,这些漏洞对全球的物联网和嵌入式设备都有广泛地影响。预估将有“数亿”台设备受到波及,涵盖智能家居设备、电网设备、医疗系统、工业装备、运输系统、打印机、路由器、移动/卫星通信设备、数据中心设备、商用飞机设备、各种企业解决方案等产品。黑客可以利用其中的一些漏洞通过网络远程执行代码展开攻击,或在设备中隐藏恶意代码,可彻底损坏入侵设备,将在整个供应链行业中产生连锁反应。由于软件供应链复杂或未被跟踪,所有使用该协议库的产品极有可能仍未打上补丁。此次发现的19个漏洞都是属于内存损坏问题漏洞,源于使用不同协议(包括IPv4,ICMPv4,IPv6,IPv6OverIPv4,TCP,UDP,ARP,DHCP,DNS或以太网链路层)在网络上发送的数据包的处理错误。

三、漏洞是如何被利用的?

看到这里,大家一定很好奇,这些漏洞是如何被利用的呢?我们拿其中的一个漏洞CVE-2020-11896具体讨论一下吧。CVE-2020-11896漏洞是Treck TCP/IP协议栈中的一个关键漏洞,是最高的严重度评分。它允许任何攻击者执行远程代码,可以将 UDP数据包发送到目标设备上的开放端口。此漏洞的一个前提条件是设备支持IP隧道的IP碎片化协议操作。

1)基本概念介绍

在讨论漏洞利用的内容之前,先来简单了解2个基本概念,便于我们更容易理解漏洞的产生和利用机制:

(1)IPv4 报文分片操作在TCP/IP协议中,由于数据链路层MTU(最大传输单元)的限制,当一个IP数据报从一个 MTU 较高的网络传向 MTU 较低的网络时数据报就会被分片成一个个大小小于或等于要通过网络的MTU碎片。分片传输示意如图1所示:

图1:报文分片传输示意图
深洋科技
http://www.plccenter.cn/about

设备A从MTU为3300的网络发送IP数据报文给MTU为1300网络中的设备B,数据报文会被分成更小的部分(“碎片”),到达设备B后再重新组包。数据报在传输过程中往往都要经过数个网络,每个网络的 MTU 或许都不同,如果数据报大小比网络的 MTU 大时就进行分片,如果比网络的 MTU 小时则不做操作,也就是说传输过程中数据报可能被多次分片,但不进行重组,重组操作由数据报的最终接收方执行。数据报文被分割的过程如图2所示:

图2:数据报文分割过程示意图

分片现象发生在IP层(网络层)。假设用户数据有5690字节数据,采用UDP传输,数据链路层MTU=1500字节,报文被分为五个分碎片传输。被分片的数据是来自上层,也就是TCP或UDP层。因此,首个分片带有TCP Header或UDP Header,其余分片不带;Fragment Offset (FO)分片偏移量,也是IPv4 Header中的一个字段。FO表示特定分片相对于原始未分片IP报文起始位置偏移量(参见图中左侧的红色虚线)。FO的单位是:8字节。例如FO=185,偏移量实际值为185*8=1480字节。

分片操作可以由源和目的之间的路由器进行,但重组操作只能由目的设备进行。这一过程就像你获得一块块拼图,拼图的背面写着它所属哪幅图(碎片识别),还有它在图中的位置(fragment offset),然后你会找个地方去放属于这幅图的碎片(初始化缓冲区),然后你会定个时间去完成拼图然后去做别的事情,即使碎片不够也不再等别人给你碎片(初始化计时器)。

(2)IP隧道技术IP 隧道技术允许两个独立网络之间的虚拟点对点链接。 它是通过将一个数据包(可能是IP 数据包)封装在另一个数据包中来实现的,从而使内部数据包具有与外部数据包不同的源地址和目的地址。所谓隧道,实际上是路由器把一种网络层协议封装到另一个协议中以跨过网络传送到另一个路由器的处理过程。发送路由器将被传送的协议包进行封装,经过网络传送,接受路由器解开收到的包,取出原始协议;而在传输过程中的中间路由器并不在意封装的协议是什么。这里的封装协议,称之为传输协议,是跨过网络传输被封装协议的一种协议,IP协议是IOS标准唯一选择的传输协议。而被封装的协议在此为IPX协议或者AppleTAlk协议,通常可以称之为乘客协议。需要特别注意的是:隧道技术是一种点对点的链接,因而必须在链接的两端配置隧道协议。我们假设在站点A和B之间交换数据。在IP协议下,数据包在路由器之间的传递直到到达目的地的过程,其线路是没有经过预先的设计和计划。然而在MPLS(多协议标记交换)协议下,在站点A和B之间传递的IP数据包必须沿由第一个路由器预先建立起来的通路传送。这条通路在IP网络中就像一条中空的隧道,直接连接A和B两个站点。隧道技术是一种数据包封装技术,它是将原始IP包(其报头包含原始发送者和最终目的地)封装在另一个数据包(称为封装的IP包)的数据净荷中进行传输。在移动IP中,隧道包目的地址就是转交地址,当外地代理(或移动节点)收到这个隧道包后,解封装该包,把里面的净荷提交给移动节点。如下图3所示:

图3:数据包封装传输示意图

IPv4主要有三种隧道技术,它们分别是:IP in IP、最小封装以及通用路由封装。

2) Treck TCP/IP 堆栈介绍

Treck TCP/IP 协议堆栈中的数据包由称为 tsPacket数据结构表示。每个数据包都与一个数据缓冲区相关联,该缓冲区保存来自接口驱动程序的原始数据。 tsPacket结构还包含另一个重要结构,称为ttUserPacket,以及指向 tsSharedData数据结构的指针,其中包括网络堆栈在处理数据包时需要的信息(指向套接字结构、源地址/目的地址或端口等的指针)。tsPacket结构包含以下几个涉及漏洞的字段。因此,首先来了解这一结构。tsPacket结构定义如下图4所示:

图4:tsPacket结构定义

 tsSharedData结构定义如下图5所示:

图5:tsUserPackte结构定义

这两种结构都是为了支持上文介绍的“IP报文分片”而构建的,因此我们接下来描述一些与分片有关的字段。我们可以把非碎片数据包看作是只有一个片段的碎片的特例。字段pktuLinkDataPtr指向当前片段的数据缓冲区。这个数据缓冲区中的确切位置随着网络堆栈在不同阶段处理数据包的不同而改变,并取决于当前正在处理的数据包的层位置。例如,当网络堆栈处理以太网层(在tfEtherRecv中)时,此字段指向以太网报头。pktuLinkDataLength字段指定pktuLinkDataPtr指向的数据的大小,即单个片段的大小。pktuLinkNextPtr用于跟踪数据包中的碎片。这个字段指向另一个tsPacket,它表示下一个片段,而下一个片段又包含对下一个片段的引用等等。出于这个原因,我们也可以在这个链表中将片段称为“链接”。如果这个链接是最后一个片段,或者如果数据不是分段的,这个字段将等于NULL。pktuChainDataLength字段表示包长度,包括所有片段,即包的总大小。它只为第一个片段设置,如果数据不是分段的,则等于“链接”数据长度。

堆栈中的一个常见模式是报文在堆栈中的层之间移动时调整 pktuLinkDataPtr指针。例如,如果我们的数据包是ICMP返回请求数据包(Ping),数据将由三层包组成:以太网,其次是IPv4,最后是ICMP。在这种情况下,当以太网层被处理时(在函数tfEtherRecv中),pktuLinkDataPtr指向以太网报头的开始处,然后在移动到下一层之前,使用以下图6所示代码进行调整:

图6:指针调整示意图

在这种情况下,0xe(14 位小数)是以太网报头(6位(目的地址MAC)+6位(源地址MAC)+ 2位(以太网类型))的长度大小。当tfEtherRecv完成数据包处理时,它使用表示下一层协议的以太网类型 EtherType字段将数据包转发到下一层处理。所支持以太网类型包括ARP、IPv4和IPv6。

图7:带有碎片数据包示意图

在图7的示例中,当IPv4层接收到数据包(在函数TFIP传入数据包中)时,指针pktuLinkDataPtr 已经指向以太网报头(IP Header),因此可以安全地假设 pktuLinkDataPtr指向的数据是IPv4报头。

传入的数据由具有相同命名约定的函数 tf*IncomingPacket处理,其中*代表的是协议名称。在以太网/IPv4/ICMP协议的不同情况下,数据包将由tfEtherRecv函数, tfIpIncomingPacket函数和tfIcmpIncomingPacket函数分别处理。Treck 堆栈处理从tfIpReassemblePacket中的碎片报文重新组装的过程,会调用tfIpIncomingPacket。每当接收到指向设备的IP片段时,都会调用此过程。如果缺少片段,则函数返回 NULL。如果所有碎片到达并且没有遗漏,网络堆栈使用 pktuLinkNextPtr字段将碎片连接在一起,并将数据包传递给下一层进一步处理。上文中,提到的“重新组装”一词并不意味着将数据包复制到一个连续的内存块,而是简单地将它们链接到一个链接列表中。

03)漏洞产生的根源

为了理解漏洞产生的根本原因,我们先来了解一下IP报文头中的两个字段:

•IHL(4位):表示IP报头的大小(以双字为单位)。最小值为5(20个字节)。如果有IP选项,则报头长度会变大,最大值为0xf(60字节)。    •Total Length总长度(2字节):整个IP数据包的大小,以字节为单位包括报头。函数tfIpIncomingPacket从一些基本的健全性检查开始。除了验证报头校验和,还会验证:

ip_version == 4 &&

data_available >= 21 &&

header_length >= 20 &&

total_length > header_length &&

total_length <= data_available

“data_available”的值是使用pktuChainDataLength来测量的。

如果所有健全性检查均通过,则该函数检查 IP 报头中指定的总长度是否严格小于数据包的pktuChainDataLength值,表明实际收到的数据比 IP 报头中所描述的要多。如果为真,则进行微调操作以删除多余的数据。判断的过程如下图:

图8:判断过程代码截图

这就是协议的漏洞所在。让我们回想一下pktuLinkDataLength的大小为当前片段和pktuChainDataLength保存整个数据包的大小。如果上述操作发生,则会创建了一个不一致情况,其中pkt->pktuChainDataLength==pkt-> pktuLinkDataLength,但可能会有其他片段指向pkt-> pktuLinkNextPtr。另一种情况,链表上片段的总长度比存储在pktuChainDataLength中的长度要大,这是构成不一致的状态。

微调操作产生的不一致性对多余数据的处理不太友好。我们还有另一个难题要解决。

每次使用一个接收到的片段都会调用 tfIpIncomingPacket函数,并会调用tfIpReassemblePacket来处理。tfIpReassemblePacket负责将片段插入到上面描述的链表中。它不会将片段复制到连续的内存缓冲区中。接收到所有片段时,tfIpReassemblePacket 将完整数据包作为片段的链接列表返回,以便在下一个协议层上进一步处理。此重组操作是在易受攻击的微调操作之后执行的。一旦重新分配操作完成,tfIpIncomingPacket将返回或转发数据包,以便在下一个网络层(例如:UDP)进行处理。因为我们需要一个碎片化的数据包才能到达不一致状态,所以这种情况阻止了我们利用漏洞。换句话说,成功攻击的代码只可能会在每个碎片的基础上执行(或者在单个碎片分组上)。 如果按上述种方式执行,实际上并不容产生漏洞。

那么,我们如何才能利用传入的碎片数据包触发易受攻击的微调数据流,以便实现上述不一致状态?

(1)处理 IP 层的碎片数据包

为了使碎片数据包在IP层被处理并获得易受攻击的数据流,我们使用IP隧道技术。隧道技术允许封装的内层IP数据包被tfIpIncomingPacket函数作为非碎片数据包处理。tfIpIncomingPacket函数将被递归调用,一次用于IP隧道的内层,几次用于外层(每个碎片一次)。首先,tfIpIncomingPacket函数将接收来自外层的所有碎片数据包,在每个数据包片段上调用tfIpReassemblePacket,全部接收完成后,它将传递到下一个协议层执行,在本例中是IPv4协议报文,所以将调用tfIpIncomingPacket处理内层IPv4数据包。

对外层IP数据包进行分段处理会导致tfIpIncomingPacket函数被内部数据包调用,该内部数据包现在由几个碎片组成,但在IP报头中被标记为非碎片(也就是MF=0)。从数据包的数据结构来看,它现在由链表中的几个单独的链接组成,每个链接都有单独的pktuLinkDataLength值。

下面举个具体的例子来讨论,我们人为设定如下内容:

• 内层IP数据包为:IPv4{len=32,protol=17}和UDP{校验和=0,len=12},有效载荷1000个‘A’。

• 外层IP数据包(片段1):IPv4{frag_offset=0,MF=1,protol=4,id=0xabcd}

从内部 IP 数据包中提取 40 字节作为有效载荷。

• 外层IP数据包(片段2):IPv4{frag_offset=40,MF=0,protol=4,id=0xabcd

},其余字节来自内层IP数据包作为有效载荷。

(在这里我们将片段1内层协议数据包中的校验和字段chechsum设置为0,这样就可以跳过UDP报文校验和验证。)

如上所述,当网络堆栈处理外层IP分段时,它将使用tsUserPacket结构中的pktuLinkNextPtr函数将片段链接起来。当tfIpIncomingPacket函数处理内层IP数据包(协议类型为4是IP-in-IP协议)时,它正在处理传入的片段数据(内层IP数据包由链接在一起的两个tsPacket结构表示),但仍将调用易受攻击的微调数据流,从而构成了漏洞利用的条件 。

此外,内层IP数据包通过了IP标头完整性检查,因为只考虑了tsUserPacket的pktuChainDataLength字段(而不是pktuLinkDataLength字段)。在我们的例子中,内层IP数据包的总长度=32,小于链接后的数据长度(1000 + 8 + 20 = 1028),因此Treck协议堆栈将尝试通过微调操作将字段pktuLinkDataLength和pktuChainDataLength都设置为相同的值,长度=32并将数据包错误地打包。 这导致由链接在一起的两个tsPacket结构的内部IP数据包的总长度为40字节,这就大于了pktuChainDataLength字段的32字节长度(注意,此时微调后pktuChainDataLength字段内长度等于32,而不是1028个字节)。

片段1(fragment1)和片段2(fragment2)的数据示例,如图9所示:

图9:示例数据包图 

(2)使用 UDP实现堆栈溢出现在我们已经有了一个定义长度和实际长度不一致的状态,接下来我们需要利用这个不一致来实现内存空间攻击。事实证明,协议中有一段代码,实现将片段数据复制到单个连续缓冲区中。 这是处理UDP数据报的代码的一部分。该操作的内部逻辑包括分配一个新的数据包(使用tfGetSharedBuffer),其大小基于pktuChainDataLength字段,然后将数据包的不同片段复制到新分配的数据包中。负责执行复制功能的函数是tfCopyPacket函数,它按顺序接受源数据包和目标数据包。以下是片段复制代码的截图:

图10:示例代码

可以看出,函数tfCopyPacket没有考虑到它写入缓冲区的长度。它只是从源数据包(我们的分段数据包)中获取每个链接,并将其数据复制到目标数据包中。 目标数据包是根据pktuChainDataLength的值分配的,因此,如果之前漏洞被触发了,则分配的缓冲区可能小于我们失效后数据包中所有单独链接的长度总和,此时,就会发生缓冲区堆溢出。 接下来,我们看看如何人为的构建触发这个过程。

如果应用程序正在侦听UDP端口,则发送到此端口的 UDP 数据包将传递给套接字处理函数TFSocket传入数据包。它的工作是将该数据包附加到套接字接收队列(稍后由应用程序轮询处理)。

在实际测试中,我们发现当UDP数据包的套接字接收队列为非空,并且有新数据包到达时,上述包含堆溢出的流是可以实现漏洞产生条件的。看看tfSocketIncomingPacket的代码截图:

图11:示例代码截图

从图11上可见,为了执行到达tfGetSharedBuffer函数调用位置,需要规避涉及socRecvCopyFraction的检查。在我们的重复示例中,第一个数据包链接具有较小的缓冲区大小,因此sizeOfPacketBuffer相对较小。当我们到达此流程时,pkt-> pktuChainDataLength等于4(微调后为32,然后在处理IP层时递减20(IP报头大小),然后再次递减8(UDP报头大小))。此时4 * 4 = 16小于sizeOfPacketBuffer的值,所以我们顺利通过了检查。

  我们需要确保漏洞被成功利用的最后一件事是要保证 UDP 数据包的接收队列是非空的。办法是,可以实例化一些线程,这些线程只会用正常的 UDP 数据包淹没设备,这样套接字接收队列就会是非空的。同时,在到达发生漏洞溢出的位置tfSocketIncomingPacket之前,我们易受攻击的数据包会通过tfUdpIncomingPacket函数。此函数包含一些与UDP相关的完整性检查,因此我们也需要绕过这些检查:

图12:示例代码截图

如图12所见,通过确保UDP长度字段等于pktuChainDataLength字段减去内部IP报头的大小,我们可以避免这种检查。

综上所述,当使用Treck TCP/IP协议栈的设备,开放UDP端口正在侦听,我们可以快速发送数据包,以便套接字接收队列为非空。同时,我们将发送一个人为构建好的片段化的UDP数据包,该数据包将触发漏洞。如果之前使用tfGetSharedBuffer在内存上分配了一个小的缓冲区,那么tfCopyPacket操作可以将其成功溢出。

我们重现了漏洞产生的条件,并成功造成缓冲区溢出,接下来就比较简单了。我们可以在堆栈上分配一个带有受控数据的原始缓冲区,可以用堆栈上的指针确定地覆盖自由列表的下一个指针,为了加大成功率,可以多次使用NOOP操作以扩大指针指向的入口地址,更容易运行事先设定的shellcode。我们还可以在执行重定向到 shellcode 之前编写一个ROP链,这样它就可以执行一些初步设置。设置主要由无效缓存组成,这样我们的shellcode就会顺利运行,最终实现远程执行代码的目的。

“Ripple20”漏洞中其他的18个漏洞原理和实现各有不同,攻击者可以利用上述的一些方法,利用漏洞通过网络远程执行代码展开攻击,或在设备中隐藏恶意代码,可以彻底损坏入侵设备,这些威胁将在整个供应链行业中产生连锁反应。

四、我们该如何应对?

多年来,由于代码更改和堆栈可配置性,Treck公司或设备制造商虽然已修补了一些“Ripple20”漏洞,但这些漏洞具有多种变体,所以安全风险仍然很大。目前Treck公司通过发布6.0.1.67或更高版本的TCP/IP堆栈来修复大多数漏洞。到目前为止,已经确认来自11个供应商的产品易受攻击,包括输液泵、打印机、UPS系统、网络设备、销售点设备、IP摄像机、视频会议系统、楼宇自动化设备和ICS设备等。但不止于此,研究人员认为这些漏洞可能会影响来自100多家供应商的数亿台设备。为此,JSOF公司安全团队提出了一些减小风险的应对建议,具体的措施如下:

1、针对设备供应商

a.确定是否使用了易受攻击的Treck TCP/IP堆栈库版本,并联系Treck公司,了解其中地风险;

b.及时更新到最新的Treck TCP/IP堆栈库版本(6.0.1.67或更高版本);

c.如果无法更新,请考虑禁用容易受到攻击的设备功能;

2、针对运营商和网络用户

a.将所有设备更新为补丁程序版本;

b.如果无法更新设备,则可以最小化嵌入式和关键设备的网络暴露,将暴露程度保持在最低水平,并确保最低限度的网络连接;

c.将物联网络和设备隔离在防火墙后,并将其与业务网络隔离;

d.仅启用安全的远程访问方法;

e.通过深度数据包检查来阻止网络攻击,以降低嵌入式程序启用Treck TCP/IP堆栈库地风险。

郑良  中孚北京研究院

http://www.plccenter.cn/about

https://www.freebuf.com/news/240479.html

http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-202006-1080https://www.freebuf.com/news/240479.htmlhttps://thehackernews.com/2020/06/new-critical-flaws-put-billions-of.html

https://www.securityweek.com/ripple20-flaws-treck-tcpip-stack-expose-millions-iot-devices-attacks