漏洞簡介
在Windows上,DNS服務器是域控制器,其管理員是Domain Admins組的一部分。默認情況下,Domain Admins組是已加入域的所有計算機上Administrators組的成員,包括域控制器[8]。如果利用得當,攻擊者可以在易受攻擊的系統上遠程執行代碼,並獲得Domain Admin權限,從而有效地損害了整個公司的基礎架構。
DNS服務器-dns.exe負責在安裝了DNS角色的Windows服務器上回答DNS查詢。
CVE-2020-1350是DNS.exe在處理畸形DNS Sig消息時,由於對數據包的字段校驗不嚴格,導致了整型溢出。DNS SIG 消息中包含對DNS記錄集合的數字簽名,DNS記錄集合就是一種名字相同,或者類型相同的DNS消息集合。
漏洞類型:整數溢出導致基於堆的緩沖區溢出
攻擊流程
攻擊者可以配置一個惡意的域名evildomain.com,該域名的DNS指向的DNS服務器作為本次攻擊的目標。
- 當客戶端查詢evildomain.com的DNS記錄;
- 目標DNS服務器向根域名服務器查詢evildomain.com的NS記錄;
- 根域名服務器告訴目標DNS,evildomain.com的權威DNS服務器是3.3.3.3,並該記錄緩存起來;
- 客戶端向目標服務器查詢evildomain.com的Sig記錄;
- 目標服務器將請求轉發給權威DNS服務器;
- 權威DNS服務器返回畸形Sig查詢結果;
目標DNS服務器處理SigQuery的畸形消息時,將此消息緩存到自己的記錄中,觸發漏洞。
漏洞函數
簡言之就是整數溢出后導致堆溢出,將該漏洞抽象為以下模型:
void Examples(char *in){
unsigned short len_p1 = len ( in.p1); unsigned short len_p2 =len ( in.p2);
unsigned short len_p = len_p1 + len_p2;
char * buf = (char *)malloc(len_p);
memcpy(buf, in.p1, len_p1); }
問題就出在 len_p 的計算上,假設 len_p1 = 0xFFD0,len_p2 = 0x40,相加后高位數據被丟棄,那么 0xFFD0 + 0x40 = 0x10010 = 0x10
,所以實際分配的內存是 0x10 而不是 0x10010,而之后往 0x10 的空間內拷貝 0xFFD0 長度的數據,自然造成了內存溢出。
而在dns.exe!SigWireRead
函數里面也有類似的溢出漏洞,如圖所示第11行:
漏洞成因如下:ushort類型,為無符號16位類型。RR_AllocateEx 分配的大小只有16個bit,大小為0~65535,所以只要構造size 大於65535,造成整數溢出,而系統就會分配一個比實際數據量小的堆塊,進而造成堆溢出,將整數溢出轉化成堆溢出漏洞。
漏洞利用:
因此,觸發此漏洞所需的全部操作就是讓受攻擊的DNS服務器向我們查詢SIG記錄,並使用長簽名(長度&>=64KB)對其作出SIG響應。但是,UDP上的DNS的大小限制為512字節(如果服務器支持EDNS0,則為4096字節)。在任何情況下,這都不足以觸發漏洞。
CheckPoint 找到一種方法可以突破上述限制:UDP 響應時將 DNS 頭部的 TC
標志置 1,表示被截斷,之后客戶端便會嘗試用 TCP 協議和服務器建立連接,並通過 TCP 協議傳遞數據。由於消息的前兩個字節表示其長度,因此TCP上的DNS中消息的最大大小表示為16位,因此限制為64KB。TCP 協議在 53 端口上可傳輸長度最大為 65535 的 DNS 數據,這無疑給該漏洞創造了先決條件。如下圖:紅框中的表示TC置位字段:
但是,即使是長度為65,535的消息也不足以觸發漏洞,因為消息長度包括報頭和原始查詢。計算傳遞給RR_AllocateEx的大小時不考慮此開銷。那么,該漏洞如何觸發呢?繼續往下看,首先了解一下域名編碼和域名壓縮。
域名編碼
eg:對於完整的一個域名www.baidu.com,需要14個字節:
\3 w w w \5 b a i d u \3 c o m
[]
可以看到第一個字節 是3,表示之后會有3個字節的字符。
域名壓縮
域名壓縮是 DNS 協議節省空間的一種方式,因為請求中的域名字符會頻繁出現在響應包的內,故采用了一種標志+偏移的格式來壓縮域名。壓縮方式很簡單:1 + 1 + offset
,即最高 2bit 位為 1 時代表了將采用域名壓縮,剩余的組合為偏移值(基地址為 DNS 頭),如下圖:響應區域中的 Name 字段為 0xc00c
,明顯是被壓縮過的,最高 2bit 位是標志,剩余的 bit 位組合得到的值為 0xc,所以當前 Name 表示的字符串在 DNS 頭部向后偏移 0xc 的位置,也就是圖中紅框中 0x3 開始(0x50 表示 DNS 頭起始位置)的域名 bbs.pediy.com
。
因此,可以將域名壓縮的偏移值指向一片可控的區域,而不是指向查詢時域名,就完全有可能使得域名的長度大大增加,比如在這里修改為c0 0d
,那么表示域名從0x62開始,說明后面有0x62個字節的字符,然后依次。這樣就使得源代碼第11行中的 sigName.Length 的大小變大。
注意:DNS name長度最大為0XFF
所以,可以在不改變原來數據包大小的情況下,將sigLength+sigName.length+0x16 的大小,大於65535,造成整數溢出。分配一個小的空間緩存,然后導致堆溢出。
POC分析和漏洞復現
部分poc代碼,大體就是構造sig響應消息。具體POC,在這里:https://github.com/maxpl0it/CVE-2020-1350-DoS
實驗環境:
Windows Server 2012 R2 192.168.29.138
KaLi Linux 192.168.29.135
復現過程:
1.在windows server 2008 安裝dns服務器,並配置靜態ip
2.在kali 中運行腳本,開啟ibrokethe.net 域名的惡意服務器,這里的ibrokethe.net 表示惡意域名,也可以代替為其他域名。
sudo python sigred_dos.py ibrokethe.net
3.在受害者服務器配置轉發器,轉發器ip為kali 機的ip。
4.在受害者服務器使用nslookup命令查詢9.ibrokethe.net:
nslookup -type=sig 9.ibrokethe.net 127.0.0.1
5.查看kali,發現會發現收到了一個UDP連接,又收到了一個TCP連接。
最后要說的是,復現過程采用了Windows DNS服務器的條件轉發器,略過了向權威NS查詢的步驟,直接向惡意DNS服務器發出查詢。
復現結果:
抓包結果如下:
由於本地無法解析“ibrokethe.net”,因此會以UDP協議向惡意服務器kali發送DNS查詢,也就是kali收到的第一個UDP連接。惡意服務器查到了該域名,就向受害者發送響應包,設置TC位,通知受害者采用TCP重發原來的查詢請求,並允許返回的響應報文超過512個字節。這是第二個DNS包。然后進行三次握手,受害者再次以TCP協議發送請求,這就是kali收到的TCP連接。之后,惡意服務器就可以利用TCP傳輸大於64KB的響應包發給受害者,觸發漏洞,造成堆溢出。
然后,就可以進行遠程代碼執行等操作了。
可以看到在復現的過程,產生堆溢出,dns.exe進程發生崩潰,從而產生DOS攻擊。如下圖:
漏洞修復
臨時修復方案
修改注冊表,限制tcp包的長度,TcpReceivePacketSize的值為0xFF00
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\Parameters
DWORD = TcpReceivePacketSize
Value = 0xFF00
注意:必須重新啟動 DNS 服務才能生效。
補丁更新
可看到右圖24行,在前面對整數溢出做了個判斷,即sigLength+sigName.Length的值要大於等於sigLength
總結:
協議方面多多少少都會有我們所疏忽的問題,對於dns.exe開發者,最開始並未考慮到最大可以超過65535,造成溢出,代碼並沒有問題,造成該漏洞的關鍵點,主要是因為域名壓縮。往往很多個疏忽點,湊到了一起就成了攻擊者利用的漏洞。
參考鏈接:
CVE-2020-1350 Windows Server DNS漏洞復現
漏洞利用剖析:帶有CVE-2020-1350 SIGRed的RCE
CVE-2020-1350: Windows DNS Server蠕蟲級遠程代碼執行漏洞分析
Windows DNS server從cve-2020-1350到內存泄露