TCP/IP協議棧系列漏洞(NAME:WRECK)分析
發布時間 2021-04-25漏洞概述
2021年04月13日,Fourscore研究實驗室與JSOF合作,披露了一組新的DNS漏洞,被稱為NAME:WRECK。這些漏洞影響了四種流行的TCP/IP堆棧--即FreeBSD、IPnet、Nucleus NET和NetX,它們普遍存在于知名的IT軟件和流行的IOT/OT固件中,并有可能影響全球數百萬的物聯網設備。攻擊者可以利用這些漏洞使受影響的設備脫機或對設備進行控制。
相關介紹
1、DNS協議之壓縮指針
在之前的文章中,我們介紹了基礎的DNS協議,其中域名是由一連串的label組成的,如下圖所示:
其中紅框所示為每個label的長度,每個label最長為63字節,并且處理的時候,除了第一個長度字節,將每個長度字節替換為”.”,最后遇到null字節結束,從而組成了最后的域名。
不過在一些回復包中,會包含多次A記錄或CNAME記錄,這就造成了DNS數據過于冗長,因此DNS協議的設計者設計出了壓縮指針。在壓縮指針的機制中,通過指針指向之前出現過的一連串label從而達到壓縮的目的。這個指針由兩個字節組成,第一個字節的前兩個bits為11,后面14個bits為偏移地址。
下面舉一個具體的例子,如下圖數據包所示,紅框內為壓縮指針,指向偏移0x0c的位置,也就是www.example.com的開頭處。
2、DHCP協議
DHCP動態主機配置協議,前身是BOOTP協議,是一個局域網的網絡協議,使用UDP協議工作,通常被用于局域網環境,主要作用是集中地管理、分配IP地址,使客戶端動態的獲得IP地址、Gateway地址、DNS服務器地址等信息,并能夠提升地址的使用率。
DHCP報文共有8種,分別如下所示:
DHCPDISCOVER :客戶端開始DHCP過程發送的報文,是DHCP協議的開始。
DHCPOFFER:服務器接收到DHCPDISCOVER之后做出的響應,包括了給予客戶端的IP(yiaddr)、客戶端的MAC地址、租約過期時間、服務器的識別符以及其他信息。
DHCPREQUEST:客戶端對于服務器發出的DHCPOFFER所做出的響應。在續約租期的時候同樣會使用。
DHCPACK:服務器在接收到客戶端發來的DHCPREQUEST之后發出的成功確認的報文。在建立連接的時候,客戶端在接收到這個報文之后才會確認分配給它的IP和其他信息可以被允許使用。
DHCPNAK:DHCPACK的相反的報文,表示服務器拒絕了客戶端的請求。
DHCPRELEASE:一般出現在客戶端關機、下線等狀況。這個報文將會使DHCP服務器釋放發出此報文的客戶端的IP地址。
DHCPINFORM:客戶端發出向服務器請求一些信息的報文。
DHCPDECLINE:當客戶端發現服務器分配的IP地址無法使用(如IP地址沖突時),將發出此報文,通知服務器禁止使用該IP地址。
DHCP數據包發送過程,如下圖所示:
DHCP報文格式如下圖所示:
部分數據域定義,如下所示:
xid:隨機生成的一段字符串,兩個數據包擁有相同的xid說明他們屬于同一次會話。
ciaddr:客戶端會在發送請求時將自己的IP放在此處。
yiaddr:服務器會將想要分配給客戶端的IP放在此處。
siaddr:引導程序中使用的下一個服務器的IP地址;由服務器在DHCPOFFER,DHCPACK中返回。
chaddr:客戶端的MAC地址。
giaddr:如果需要跨子網進行DHCP地址發放,則在此處填入經過的路由器的IP地址。
sname:服務器主域名。
options:可以自由添加的部分,用于存放客戶端向服務器請求信息和服務器的應答信息。
DHCP域搜索選項,該選項從DHCP服務器傳遞到DHCP客戶端,以指定在使用DNS解析主機名時使用的域搜索列表。該選項的代碼為119,格式如下圖所示:
舉個例子,下圖是“eng.apple.com”和“marketing.apple.com”組成的搜索列表的示例編碼:
該示例編碼已分為三個“域搜索選項”。在客戶端解析之前,所有域搜索選項在邏輯上都串聯到一個數據塊中。以第一個“域搜索選項”為例,第一個字節為119,第二個字節為9,表示后面Searchstring的長度,剩下的數據均為Searchstring。這三個“域搜索選項”的Searchstring組合成一個完整聚合塊,可表示為:
|3|’e’|’n’|’g’|5|’a|’p’|’p|’l’|’e’|3|’c’|’o’|’m’|0|9|’m’|’a’|’r’|’k’|’e’|’t’|’i’|’n’|’g’|0xC0|0x04|。
“eng.apple.com”的編碼以零結尾,以標記名稱的結尾?!癿arketing”(針對marketing.apple.com)的編碼以兩個八位字節的壓縮指針C004(十六進制)結尾,該指針指向DomainSearchOption數據的完整聚合塊(從第一個“域搜索選項”中的Searchstring開始)中的偏移量4,其中另一個有效編碼可以找到完整的域名(“apple.com”)。如下圖所示:
每個搜索域名都必須以零或兩個八位位組壓縮指針結尾。如果接收器到達搜索列表選項數據的完整匯總塊的末尾時正在通過搜索域名進行解碼,而沒有找到零或有效的兩個八位位組壓縮指針,那么必須將部分讀取的域名視為無效域名。
漏洞分析
1、Nucleus NET系列漏洞
上圖為Nucleus NET協議棧中的DNS_Unpack_Domain_Name()函數,這個函數用來處理DNS應答記錄。第一個參數dst是指向一個buffer,用于拷貝解析的域名。第二個參數src指向域名的第一個字節,第三個參數指向DNS Header的第一個字節。
代碼通過while循環去解析域名(第7行),直到src為null字節,也就是域名的結尾。之后將第一個label的長度賦值給size(第9行)。下面,也就是最重要的一步就是檢查該字節是否為壓縮指針。如果不是,src指針前移一個字節,然后將src拷貝到dst。然后每個label之間加”.”。為正常的解析域名流程。如果是壓縮指針而且是第一個壓縮指針,retval加兩個字節(第10,11行),然后根據偏移計算label起始位置,然后將長度賦值給size,之后正常處理。
這段代碼看起來沒有問題實際上包含4個非常嚴重的問題,其造成的漏洞編號分別為CVE-2020-27736、CVE-2020-27738、 CVE-2020-15795、CVE-2020-27009。
(1)對label長度沒有做驗證
根據上文講述的內容,每個label的第一個字節代表長度(第8,16行),但是程序沒有檢查這個長度是否代表真實數據包中label的長度,這可導致讀取超過已分配結構體的buffer,造成拒絕服務。
(2)對壓縮指針的偏移沒有做驗證
根據上文講述的內容,程序判斷為壓縮指針后,便會通過給出的偏移去尋找解析label(第14,15,16行),但是程序沒有驗證偏移的范圍,這導致偏移值可以任意給定,這導致可以越界讀寫,從而RCE。
(3)缺少NULL終止判定
根據RFC1035的表述,NULL字節(0x00)表示name的結尾(第7行)。但是在很多DNS解析程序中缺乏對NULL字節的驗證,這導致攻擊者可以通過控制NULL字節在特定的位置,通過和前幾個問題相結合,同樣可以實現可控的內存讀寫。
(4)對域名的長度沒有做限制
根據RFC1035的陳述,從DNS記錄里提取的域名不應該超過255字節,盡管每個label限制了不超過63個字節(第15行),但是這只是一次性拷貝的長度,并沒有限制實際拷貝的長度。如下圖所示,通過NU_Allocate_Memory()函數分配給name 255個字節個空間(第50行),即DNS_MAX_NAME_SIZE。后面調用DNS_Unpack_Domain_Name的過程中,顯然可以通過構造惡意的數據包溢出這255字節。
所以根據以上內容,PoC的編寫的思路有很多方法,比如可以讓程序永遠無法退出循環,類似下圖:
c0為壓縮指針,1e為偏移量,而偏移的位置正好重新指向c0,造成無限循環。
2、FreeBSD漏洞(CVE-2020-7461)
該漏洞出現在dhclient解析DHCP數據包中的“域搜索選項”數據時,由于邊界檢查錯誤而導致堆溢出。dhclinet是FreeBSD系統中用于提供DHCP服務的二進制程序,執行命令:dhclient em0便可進行DHCP配置,em0為網卡。源碼位于sbin\dhclient\options.c,從do_packet()函數開始,代碼清單如下圖所示:
該函數用于處理DHCP客戶端接收到的數據包,行890,調用parse_options()函數解析數據包中的options,代碼清單如下圖所示:
行106,調用expand_domain_search()函數進一步解析DHCP域搜索選項數據,該函數進行兩個操作,第一步操作是先獲取所有域名標簽的總長度,第二步操作是根據第一步獲取的長度進行內存分配,并拷貝所有域名標簽。
先看第一操作,如何獲取所有域名標簽的長度,關鍵代碼實現如下圖所示:
首先判斷options是否為空,不為空就獲取options,行229,然后進入while循環,調用find_search_domain_name_len()函數處理options,該函數通過一個while循環逐個字節解析Searchsting并分類處理,第一種情況的關鍵代碼實現如下圖所示:
如果讀取到data[i]為0,表示域名標簽的結尾,并返回該域名標簽長度。接著第二種情況的關鍵代碼實現如下圖所示:
如果讀取到data[i]為0xC0,表示為壓縮指針,指向另一個域名標簽,行287,計算pointer,然后對該指針進行范圍檢查判斷是否越界(第299行),遞歸調用find_search_domain_name_len()函數解析壓縮指針指向的另一個域名標簽(第301行),遞歸調用返回后,進行domain_name_len += pointer_len累加。如果既不是0結尾也不是壓縮指針,則依次累加并移位游標,實現代碼如下圖所示:
在第299行和第301行之間是存在問題的,如果遞歸處理壓縮指針指向的另一個域名標簽不合法時,返回的pointer_len為-1,這里并沒有將其視為無效并進行返回,而是依舊返回部分域名標簽長度。
再看第二操作,分配緩沖區并進行域名標簽拷貝,關鍵代碼如下圖所示:
這里expanded_len為計算出來的域名標簽的長度(第242行),分配一段內存(第248行),進入while循環,調用expand_search_domain_name()函數進行域名標簽拷貝,實現代碼如下圖所示:
該函數和find_search_domain_name_len()函數實現基本是一樣的,分類處理0結尾和壓縮指針,但是在處理壓縮指針的情況時,并沒有判斷指針范圍的合理性。行366,調用memcpy進行域名標簽拷貝,拷貝長度為label_len。
根據前文分析,第一步操作在遞歸處理壓縮指針的情況時,是存在問題的,并沒有丟棄包含無效壓縮指針的域名標簽??梢詷嬙煲粋€特殊的域名標簽混淆lebal的遞歸解析,該域名標簽表示為:| 1 |x3F|x00| 1 |'A'|xC0|x01|。
依次讀取到label_len為0xC0時,進一步遞歸調用解析,這次讀取到label_len為0x3f,將0x3f當成了域名長度,但是第305行,判斷發生越界,因此返回-1。
但是這里仍然計算出domain_name_lan為1,因此第二個域名包含一個0x41。
最后計算出expanded_len為0x5,如下圖所示:
然后開始進行第二步拷貝操作,先拷貝第一個domain:0x3f。如下圖所示:
第二次拷貝第二個domain:0x41。如下圖所示:
遞歸解析壓縮指針0XC004時,發生了混淆,如下圖所示:
錯誤地將0x3f當成label_len,這明顯是大于expanded_len的,直接拷貝導致溢出。不過在實際測試中,并未產生內存破壞,而可以構造其他的域名標簽陷入無限遞歸,讓dhclient進程堆棧耗盡導致崩潰,造成拒絕服務。
處置建議
FreeBSD、Nucleus NET和 NetX,建議先實施以下安全建議,再及時更新設備供應商發布的安全更新。
安全建議:
使用一些緩解信息來開發檢測DNS漏洞的簽名;
發現并清點運行易受攻擊堆棧的設備;
實施分段控制和適當的network hygiene;
監視受影響的設備供應商發布的補??;
配置設備依賴內部DNS服務器;
監控所有網絡流量中的惡意數據包。