原創 | Ripple20:Treck TCP/IP協議棧漏洞分析與驗證

發布時間 2020-06-30

一、前言


國外安全研究人員在由Treck開發的TCP/IP協議棧中發現了多個漏洞,這一系列漏洞統稱為Ripple20。這些漏洞廣泛存在于嵌入式和物聯網設備中,影響了多個行業領域(包括醫療、運輸、能源、電信、工業控制、零售和商業等),涉及了眾多供應商(包括HP、Schneider Electric、Intel、Rockwell Automation、Caterpillar、Baxter等)。


這些漏洞源于Ripple20的多個協議(包括IPv4、ICMPv4、IPv6、IPv6OverIPv4、TCP、UDP、ARP、DHCP、DNS或以太網鏈路層)在處理網絡報文發送時存在缺陷,其中包括四個嚴重漏洞,它們的CVE編號分別為CVE-2020-11896、CVE-2020-11898、CVE-2020-11910、CVE-2020-11911。CVE-2020-11896(CVSS評分10)可導致遠程執行代碼,CVE-2020-11897(CVSS評分10)可導致越界寫入,CVE-2020-11901(CVSS評分9)可導致遠程執行代碼,CVE-2020-11898(CVSS評分9.1)可導致泄露敏感信息。其它15個Ripple20漏洞的嚴重程度各異,CVSS評分分別從3.1到8.2。


由于物聯網設備供應鏈的特性,漏洞影響的設備眾多,影響范圍廣且持續時間長,漏洞修復的實施較困難。因此,啟明星辰ADLab第一時間對相關漏洞進行了分析并提出了防范建議。


二、協議棧檢測


由于采用Treck協議棧的廠家較多,有些廠家是硬件IP核的方式引用了Treck協議棧。單純通過設備指紋來識別漏洞是不足的,如何檢測目標設備是否為Treck協議棧成為資產排查的關鍵,為此啟明星辰ADLab安全研究員對Treck協議棧進行了深入分析,并公開了Treck協議棧指紋檢測方法發現漏洞。


Treck協議棧自定義了類型為165(0xa5)的ICMP包,并一旦收到165的ICMP包會回復類型為166的ICMP包響應。如下代碼所示:



首先,向目標發送 ICMP請求包,其中type=0xa5,code=0。如下圖所示:



然后,接收目標返回的icmp響應包數據,其中type =0xa6,code =0,ICMP報文第9字節后的六個字節為0x01,0x51,0x35,0x28,0x57,0x32(大端)或0x51,0x01,0x28,0x35,0x32,0x57(小端)。


滿足上述的條件,則表明目標設備為treck 協議棧。如下圖所示:



三、防范建議


1、應用更新


及時更新到Treck TCP/IP協議棧軟件的最新穩定版本(6.0.1.67或更高版本)。


2. 阻止異常IP流量


可以通過深度數據包檢查來阻止網絡攻擊,以下是可以適當應用于網絡環境中的可能緩解措施,過濾選項包括:


● 如果網絡環境不支持,則規范化或拒絕IP分片的數據包(IP分片)

● 如果不需要,請禁用或阻止IP隧道(IPv6-in-IPv4或IP-in-IP隧道)

● 阻止IP源路由和所有不贊成使用IPv6的功能,例如路由標頭

● 強制執行TCP檢查并拒絕格式錯誤的TCP數據包

● 阻止未使用的ICMP控制消息,例如MTU更新和地址掩碼更新

● 通過安全的遞歸服務器或應用層防火墻規范DNS

● 確保網絡環境中使用的是可靠的OSI第2層設備(以太網)

● 通過DHCP偵聽等功能提供DHCP / DHCPv6安全性

● 如果未在交換基礎架構中使用,則禁用或阻止IPv6多播。


四、相關概念介紹


1、IP分片


IP分片使得在網絡中發送大的IP包成為可能,即使其大小大于網絡特定鏈路中允許的最大值。IP分片技術是一種將數據包分成幾個較小的部分以支持通過這些鏈路和網絡傳輸的技術。該協議支持在發送端進行分片,然后在接收端對分片重新組合。這允許不同的包在網絡中零散地傳輸,并在另一側正確地重新組裝。


不同的包使用IP頭中的標識字段(Identification)進行分組。此標識字段描述分片屬于哪個包。同一個包的多個分片的Identification是一樣的。IPv4通過Flags及Fragment Offset字段對分片進行管理,Flags由R、DF、MF三部分組成:


● R(Reserve bit)保留未用

● DF (Don't Fragment) DF =1:禁止分片 , DF =0:允許分片

● MF (More Fragment) MF =1:非最后一片, MF =0:最后一片(或未分片)


Fragment Offset(13位):一個IP分組分片封裝原IP分組數據的相對偏移量, 片偏移字段以8字節為單位。IP包結構如下圖所示:



2、IP隧道技術


IP隧道允許兩個獨立網絡之間的虛擬點到點鏈路。它是通過將包(可以是IP包)封裝在另一個包中來實現的,使得內部包具有與外部包不同的源地址和目標地址。外部包的源地址和目標地址是隧道端點,內部包中的地址用于隧道兩端的網絡路由。隧道入口點是接收應通過隧道轉發的IP數據包的節點。它將此數據包封裝在外部IP數據包中。當數據包到達隧道出口點時,它被解封裝并轉發,就好像它是在目標網絡中發送的常規數據包一樣。IP-in-IP包如下圖所示:



IP隧道技術主要應用在虛擬專用網(VPN)技術中。目前有幾種隧道協議,其中最簡單和最古老的是IP-in-IP(IP協議編號4)。IP-in-IP是一種IP隧道協議,其中一個IP包通過添加一個外部IP報頭(其源地址和目標地址分別等于隧道的入口點和出口點)封裝在另一個IP包中。內部數據包未被修改,外部IP頭從內部IP頭復制一些字段。外部報頭的IP協議號為4。IP-in-IP報文示例如下圖所示:



五、Treck協議棧


1、協議棧概述


Treck協議棧通過tsPacket結構來描述包結構,通過tsUserPacket結構支持數據包分片。這兩個結構體在treck/include/trsocket.h文件中定義。Treck TCP/IP協議棧中的包數據由tsPacket的結構表示。每個包都與一個數據緩沖區相關聯,該數據緩沖區保存從接口驅動程序到達的原始數據。tsPacket結構還保存另一個稱為ttUserPacket的重要結構,以及指向tsSharedData結構的指針,該結構包括網絡協議棧處理數據包時所需的信息(指向套接字結構、src/dst地址或端口等的指針)。定義如下:


struct tsPacket {

ttUserPacket pktUserStruct;

ttSharedDataPtr pktSharedDataPtr;

struct tsPacket * pktChainNextPtr;

struct tsDeviceEntry * pktDeviceEntryPtr;

union anon_union_for_pktPtrUnion pktPtrUnion;

tt32Bit pktTcpXmitTime;

tt16Bit pktUserFlags;

tt16Bit pktFlags;

tt16Bit pktFlags2;

tt16Bit pktMhomeIndex;

tt8Bit pktTunnelCount;

tt8Bit pktIpHdrLen;

tt8Bit pktNetworkLayer;

tt8Bit pktFiller[1];

};


這是包含的ttUserPacket結構(tsUserPacket的typedef),定義如下:


struct tsUserPacket {

void * pktuLinkNextPtr; // Next tsUserPacket for fragmented data

ttUser8BitPtr pktuLinkDataPtr;

ttPktLen pktuLinkDataLength;

ttPktLen pktuChainDataLength;

int pktuLinkExtraCount;

};


pktuLinkNextPtr :用于跟蹤數據包中的分片。此字段指向表示下一個分片的另一個tsPacket結構,該tsPacket還保存對下一個分片的引用,如果此鏈接是最后一個分片,或者數據未被分片,則此字段將為NULL。


pktuLinkDataPtr:指向當前分片的數據緩沖區。當Treck協議棧在不同階段處理數據包時,數據緩沖區中的確切位置會發生變化,這取決于當前正在處理的數據包所在協議層。例如,當Treck協議棧處理以太網層(在tfEtherRecv()函數中)時,此字段指向以太網報頭。


pktuLinkDataLength:pktuLinkDataPtr指向的數據的大小,即單個分片的大小。

pktuChainDataLength:表示包含所有分片的數據包長度,即數據包的總大小。它只為第一個分片設置。如果數據沒有分片,則等于pktuLinkDataLength。


2、協議棧處理過程


協議棧中的一個常見模式是在協議棧中的層之間移動時調整pktuLinkDataPtr指針。例如,如果我們的包是一個ICMP回顯請求包(ping),它的協議由三層組成:Ethernet、IPv4、ICMP。在這種情況下,當處理以太網層(在tfEtherRecv()函數中)時,pktuLinkDataPtr指向以太網報頭的開始,然后在移動到下一層之前,使用以下代碼對其進行調整,如下代碼所示:



在本例中,0xe(十進制為14)是以太網報頭(6(dst MAC)+6(src MAC)+2(etherType))的大小。當tfEtherRecv()函數完成包處理時,它將包轉發到下一層處理。支持的以太網類型有ARP、IPv4和IPv6。如下代碼所示:



在示例中,當IPv4層接收到數據包(在函數tfIpIncomingPacket()函數中)時,指針pktuLinkDataPtr已經指向IP報頭。傳入數據由具有相同命名約定tf*IncomingPacket的函數處理,其中*是協議名。對于ICMP包來說,它由三層協議組成(Ethernet/IPv4/ICMP),數據包將由函數tfEtherRecv、tfIpIncomingPacket和tfIcmpIncomingPacket函數分別處理。


3、分片重組


Treck協議棧在tfIpReassemblePacket()函數中處理分片的重組,該函數由tfIpIncomingPacket()調用。每當接收到發往設備的IP分片時,就會調用此函數。如果缺少分片,函數將返回NULL。否則,如果所有分片都到達并且沒有空洞,則網絡協議棧將使用pktuLinkNextPtr字段將分片鏈接在一起,然后將數據包傳遞給下一層進行進一步處理。在此上下文中,“重組”一詞并不意味著將數據包復制到連續的存儲塊,而只是簡單地將它們鏈接到一個鏈表中。分片數據鏈表結構如下圖所示:


4、tfIpIncomingPacket函數


tfIpIncomingPacket()函數是處理IP包的主要函數,該函數主要流程如下圖所示:



tfIpIncomingPacket()首先判斷數據包合法性。tfIpIncomingPacket()函數除了驗證IP頭校驗和,它還進行以下驗證,如下代碼所示:



然后如果所有合法性檢查都通過,tfIpIncomingPacket()函數將檢查IP報頭中TotalLength 是否嚴格小于數據包的pktuChainDataLength,這表示實際接收的數據比IP報頭中聲明的數據多。如果是真的,則進行修剪操作,要刪除額外的數據,如下代碼所示:



再者如果IP數據包的MF為1或者Fragment Offset大于0,則tfIpIncomingPacket()函數就要調用tfIpReassemblePacket()函數進行分片重組。如果IP分片數據接收不完整,則tfIpReassemblePacket()函數返回NULL。如果所有IP分片都到達并且沒有錯誤,則Treck協議棧使用pktuLinkNextPtr字段將這些分片鏈接在一起,建立鏈表,并將包傳遞到下一層進行進一步處理,如下代碼所示:



最后如果已經收到完整的IP數據包,則tfIpIncomingPacket()函數根據IP數據包中的協議字段的協議號,調用相應的協議包處理函數進行處理。在下列代碼中,當協議號為UDP時,則調用tfUdpIncomingPacket()函數,當數據包協議為IP-in-IP協議(協議號4)時,會遞歸調用tfIpIncomingPacket()函數,代碼實現如下所示:




六、漏洞原理分析


1、CVE-2020-11896


前文已經介紹tfIpIncomingPacket()函數的實現過程,第二步的數據裁剪是漏洞的原因,如下代碼所示:



pktuLinkDataLength保留當前分片的大小,pktuChainDataLength保留整個IP數據包的大小。如果執行上述操作,將導致一個不一致性的狀態,其中pkt->pktuChainDataLength==pkt->pktuLinkDataLength,但可能有pkt->pktuLinkNextPtr指向其他分片。更進一步的其中鏈表上分片的總數據大小可能大于存儲在pktuChainDataLength變量中的大小。這種操作導致的不一致性將會導致后續報文處理發生異常。


通過簡單地設置錯誤的IP包分片是無法觸發漏洞的,因為裁剪過后的分片數據在后續的tfIpReassemblePacket()函數操作中會根據pktuChainDataLength的大小,重新建立分片鏈表,不會造成不一致的狀態。理想的流程是先完成分片鏈表的建立,再進行鏈表數據總大小的裁剪流程,這樣就會進入不一致的狀態。


為了在IP層處理分片數據包并觸發執行有問題的流程代碼,可以使用IP-in-IP數據包。對于分片的IP-in-IP數據包,tfIpIncomingPacket()函數將至少遞歸調用兩次,一次用于IP隧道包的內層IP數據包,多次用于外層IP數據包(每處理一個外層IP包分片算作一次)。


tfIpIncomingPacket()函數在處理IP隧道數據包的時候將內部IP數據包作為非分片數據包進行處理。內部數據包現在由多個分片組成,但在IP報頭中標記為非分片(MF=0),所以它不會再進入tfIpReassemblePacket()函數進行重組。它現在由一個鏈表中的幾個單獨的tsPacket鏈接組成,每個鏈接都有一個單獨的pktuLinkDataLength值??紤]下面的例子,它將有助于理解漏洞的成因:


● Inner IP packet: IPv4{len=32, proto=17}/UDP{checksum=0, len=12},其中包括1000字節的數據’A’。

● Outer IP packet (fragment 1): IPv4{frag offset=0, MF=1, proto=4, id=0xabcd} ,其中包括40字節的IP數據。

● Outer IP packet (fragment 2): IPv4{frag offset=40, MF=0, proto=4, id=0xabcd} ,其中數據負荷為988字節。


為了繞過UDP校驗,將校驗和字段checksum設置為0。實例中的分片結構如下圖所示:



當Treck協議棧處理外部分片時,它使用tsUserPacket結構中的pktuLinkNextPtr字段來鏈接它們。如前所述,當tfIpIncomingPacket()函數處理內部IP數據包(協議為4,IP-in-IP)時,它已經完成了分片數據的重組(內部IP數據包由鏈接在一起的兩個tsPacket結構表示)。分片數據重組后的鏈表結構如下圖所示:



由于tfIpIncomingPacket()函數在進行有效性判斷時,只考慮tsUserPacket中的pktuChainDataLength字段(而不是pktuLinkDataLength),所以在處理內部IP包時將進入錯誤的鏈表長度的裁剪流程,從而導致了問題。


內部IP包通過了IP頭完整性檢查,在該例子中,內部IP包的總長度(32)小于鏈表數據長度(1000+8+20=1028),因此Treck協議棧將嘗試錯誤地修剪數據包,方法是將字段pktuLinkDataLength和pktuChainDataLength設置為相同的值ipTotalLength(在我們的示例中為32)。這導致內部IP數據包由鏈接在一起的兩個tsPacket結構表示,但它們的數據總長度大于pktuChainDataLength字段(修剪后pktuChainDataLength字段不是1028字節,而是等于32)。經過數據長度裁剪后的鏈表結構如下圖所示:



現在已經使得鏈表達到了不一致的狀態,下面將介紹如何利用這種不一致的狀態來導致內存破壞。


在Treck協議棧代碼中至少有一個代碼路徑可以將分片數據復制到單個連續緩沖區中。具體的執行路徑為:

tfUdpIncomingPacket() ->  tfSocketIncomingPacket() -> tfCopyPacket()。下面的代碼是tfSocketIncomingPacket()函數處理UDP數據報的代碼的一部分,如下代碼所示:



這段代碼中tfSocketIncomingPacket()函數調用tfGetSharedBuffer()申請內存,其大小基于pktuChainDataLength字段的值,然后通過tfCopyPacket()函數將數據包的不同分片逐個復制到新分配的內存空間中,發生溢出的代碼如下所示:



由于兩個分片中的pktuLinkDataLength之和為1000字節,后續的tfCopyPacket函數將會把1000字節的數據拷貝到這段內存中,這將導致堆溢出。


2、CVE-2020-11898


正如前文描述如何觸發CVE-2020-11896漏洞那樣,Treck TCP/IP協議棧無法正確處理通過IP-in-IP隧道傳入的IPv4分片。這也可能允許未經身份驗證的攻擊者從堆中泄漏內存??梢圆捎靡韵率纠M行漏洞觸發:


● 內部IP數據包:IPv4 {ihl = 0xf,len = 100,proto = 0},有效載荷為'\ x00'* 40 +'\ x41'* 100。

● 外部IP數據包(分片1):IPv4 {frag offset = 0,MF = 1,proto = 4,id = 0xabcd},其中24個字節來自內部IP數據包有效負載。這意味著將復制20個字節的IP標頭,外加4個空字節。

● 外部IP數據包(分片2):IPv4 {frag offset = 24,MF = 0,proto = 4,id = 0xabcd},來自內部IP數據包的其余字節作為有效負載。


實例中的分片結構如下圖所示:



這里ihl為0xf,表示為最大IP選項,長度為60字節,數據包總長度total_length為100。當網絡協議棧收到兩個分片時,它將使用tfIpReassemblePacket()函數重新組裝它們。分片數據重組后的鏈表結構如下圖所示:



該tfIpReassemblePacket()函數使用tsUserPacket結構中的字段pktuLinkNextPtr鏈接兩個分片。如果啟用了IP-in-IP隧道傳輸,則內部IP數據包將隨后由tfIpIncomingPacket()函數中處理,修剪后的pktuChainDataLength字段不是160,而是等于100。經過數據長度裁剪后的鏈表結構如下圖所示:



內部IP數據包通過IP標頭完整性檢查,因為僅考慮了tsUserPacket的pktuChainDataLength字段(而不考慮pktuLinkDataLength)。因為在標準IP頭部(20個字節)之后有4個空字節,并且一個空字節代表IP選項的末尾,IP選項解析通過檢查。由于內部IP數據包包含無效的IPv4協議編號(Protocol為0),進入default分支,然后直接進入TM_IP_LOCAL_FLAG分支。如下代碼所示:



因此網絡協議棧將通過發送類型為3(目標不可達)和代碼為2(協議不可達)的ICMP錯誤消息來拒絕該數據包。如下代碼所示:



負責創建錯誤數據包的是tfIcmpErrPacket()函數。它分配一個新的數據包,初始化一些ICMP頭部字段。如下代碼所示:



最后從后續數據包(內部IP數據包)中復制一些數據。復制部分如下代碼所示:



如代碼所見,tfIcmpErrPacket()函數通過獲取IP報頭長度(以字節為單位加上8,在實際情況下為60 + 8 = 68)與pktuLinkDataLength字段(以及被裁剪為100)之間的最小值來計算要復制的字節數 。由于發送數據包的第一個分片的實際鏈路數據長度為24(而不是100),因此tfIcmpErrPacket()函數將從堆中復制68-24 = 44字節的額外數據。然后設置v12_icmpErrPacket中相關數據。如下代碼所示:



最后調用tfIpSendPacket()函數發送icmp_ErrPacket包到目標地址,這將導致44字節的信息泄露。


3、CVE-2020-11910


CVE-2020-11910是越界讀漏洞,該漏洞存在tfIcmpIncomingPacket函數中,該函數主要是處理ICMP包。tfIcmpIncomingPacket函數在處理設備收到類型為3,code為4的ICMP包的時候,代碼并沒有驗證后續數據的長度,直接就訪問了對應位置的數據,造成了越界讀漏洞。如下代碼所示:



4、CVE-2020-11911


CVE-2020-11911是未授權的敏感信息更新漏洞,該漏洞存在tfIcmpIncomingPacket函數中,該函數主要是處理ICMP包。tfIcmpIncomingPacket函數在處理設備收到類型為18(Address mask reply)包的時候,代碼并沒有驗證設備是否發送過類型17(Address mask request)請求,就直接更新了設備的子網掩碼。如下代碼所示:



七、CVE-2020-11898漏洞驗證


遠程攻擊打印機,視頻請到微信公眾號中查看:驗證視頻。


八、參考


1、https://www.ietf.org/rfc/rfc2003.txt

2、https://www.ietf.org/rfc/rfc792.txt

3、https://www.ietf.org/rfc/rfc1853.txt

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

5、https://kb.cert.org/vuls/id/257161

6、https://www.venustech.com.cn/article/1/11834.html

7、JSOF_Ripple20_Technical_Whitepaper_June20.pdf



啟明星辰積極防御實驗室(ADLab)


ADLab成立于1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員,“黑雀攻擊”概念首推者。截止目前,ADLab已通過CVE累計發布安全漏洞1000余個,通過 CNVD/CNNVD累計發布安全漏洞800余個,持續保持國際網絡安全領域一流水準。實驗室研究方向涵蓋操作系統與應用系統安全研究、移動智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、云安全研究。研究成果應用于產品核心技術研究、國家重點科技項目攻關、專業安全服務等。