更多安全資訊和分析文章請關注啟明星辰ADLab微信公眾號及官方網站(adlab.venustech.com.cn)
數月前,國外安全組織ZDI研究人員披露了一個Linux內核本地權限提升漏洞,該漏洞出現在流量控制子系統包分類器的cls_route過濾器中,當舊過濾器句柄為0時,在釋放之前內核不會從哈希表中將其刪除,其漏洞編號為CVE-2022-2588,而且還提出了一種新的漏洞利用方法,命名為DirtyCred,該方法可繞過廣泛採用的內核保護和漏洞利用緩解措施,從而實現權限提升。Rtnetlink是所有內核網絡子系統使用的網絡連接總線,包括網絡接口、路由、fdb和鄰居。一些內核網絡子系統也在通用netlink總線上提供服務。Linux內核網絡子系統使用消息類型和系列向Rtnetlink內核註冊處理程序。Rtnetlink允許讀取和更改內核的路由表。它在內核中用於在各種子系統之間進行通信,也用於與用戶空間程序進行通信。網絡路由、IP地址、鏈接參數、鄰居設置、排隊規則、流量類別和數據包分類器都可以通過NETLINK_ROUTE套接字進行控制。Rtnetlink由以下消息類型組成(除了標準的netlink消息):RTM_NEWLINK、RTM_DELLINK、RTM_GETLINK創建、刪除或獲取有關特定網絡接口的信息。
RTM_NEWADDR、RTM_DELADDR、RTM_GETADDR添加、刪除或接收有關與接口關聯的IP地址的信息。
RTM_NEWROUTE、RTM_DELROUTE、RTM_GETROUTE創建、刪除或接收有關網絡路由的信息。
RTM_NEWNEIGH、RTM_DELNEIGH、RTM_GETNEIGH添加、刪除或接收有關鄰居表條目的信息(例如,ARP條目)。
RTM_NEWRULE、RTM_DELRULE、RTM_GETRULE添加、刪除或檢索路由規則。
RTM_NEWQDISC、RTM_DELQDISC、RTM_GETQDISC添加、刪除或獲取排隊規則。
RTM_NEWTCLASS、RTM_DELTCLASS、RTM_GETTCLASS添加、刪除或獲取流量類別。
RTM_NEWTFILTER, RTM_DELTFILTER, RTM_GETTFILTER添加、刪除或接收有關流量過濾器的信息。
當內核啟動加載,在初始化netlink協議實現時,會調用rtnetlink_init()函數初始化路由netlink socket接口,該函數實現如下:根據代碼可知,通過rtnl_register()函數將不同的消息類型和對應操作進行綁定,該函數簽名為void rtnl_register(int protocol, int msgtype,rtnl_doit_func doit, rtnl_dumpit_func dumpit,unsigned int flags),有的消息類型只有動作函數doit,有的消息類型只有dump函數dompit,有的消息類型兩者皆有。有的消息類型例如RTM_NEWTFILTER,添加一個流量過濾器,則是在tc_filter_init()函數中初始化,該函數實現如下:當用戶層通過NETLINK_ROUTE套接字發送RTM_NEWTFILTER消息用於創建一個流量過濾器時,內核將調用rtnetlink_rcv_msg()函數來處理rtnetlink消息,該函數關鍵實現如下:從消息中獲取family和type,然後調用rtnl_get_link()函數根據family和type獲取link,行5246,調用link->doit回調函數,這裡的doit回調函數即為tc_new_tfilter()函數。該函數會進一步解析rtnetlink消息數據包,判斷並創建哪種類型的過濾器,具體實現如下:獲取指定的過濾器名字,然後會獲取指定協議的過濾器tp。如下代碼所示:如果沒有,便根據name創建一個新的tp。如下代碼所示:
判斷tca[TCA_KIND]不為空和nlmsg_flags為NLM_F_CREATE,然後調用tcf_proto_create()函數創建,該函數實現如下:行258,分配一個tp;行262,調用tcf_proto_lookup_ops()函數根據kind獲取對應的ops。這裡以route為例,將獲取到cls_route4_ops,如下代碼所示:然後初始化tp,行274,調用route4_init()函數初始化一個route4_head,用於存放過濾器對應的哈希值,該函數實現如下:tcf_proto創建完成後,將其插入chain中。如下代碼所示:接下來調用對應的get函數,根據tcm_handle獲取過濾器。這裡將調用route4_get()函數獲取,該函數實現如下:根據handle從route4_head鍊表中獲取對應的route4_filter。如果為空,便調用change函數進行創建。如下代碼所示:這裡將調用ruote4_change()函數進行創建,該函數具體分析見下文。創建成功後,添加一個新的route4過濾器的整個流程便完成了。漏洞代碼出現在route4_change()函數中,該函數實現如下:行483,首先進一步解析消息數據包;行488,拿出傳入的route4_filter,然後判斷是否已創建,如果創建過,再判斷handle是否一致。由於第一次創建,這裡fold為空。接下來進行創建並初始化,如下代碼所示:行493,調用kzalloc()函數分配route4_filter,該結構體大小為144字節。行497,調用tcf_exts_init()函數進行初始化,該函數實現如下:如果內核開啟了CONFIG_NET_CLS_ACT,便調用kcalloc()函數分配exts->actions,分配大小為256字節。初始化完成返回到route4_change()函數中,行501,如果fold不為空,便將fold的數據域賦值給f。行512,然後調用route4_set_parms()函數設置其他參數。行517到行527,將新創建的route4_filter對應的哈希值放到route4_head中。如下代碼所示:行529到行543,該段代碼是將舊過濾器的哈希值從route4_head中移除。如下代碼所示:由於是第一次創建,fold為空,因此不進入。但是行529代碼是有問題的,這裡不僅判斷fold是否為空,同時還判斷fold->handle是否為空。那如果第一次創建一個handle為0的過濾器,第二次創建新過濾器時,fold不為空,但是fold->handle為0,因此並不會將handle為0的舊過濾器的哈希值從route4_head中移除,保留了對其的索引。接下來開始釋放舊過濾器內存,如下代碼所示:第一次創建過濾器時,fold為空,不進入。當第二次創建新過濾器時,fold不為空,行550,調用tcf_queue_work()函數對handle為0的舊過濾器進行釋放操作,回調函數為route4_delete_filter_work()函數,該函數實現如下:行266,最後調用__route4_delete_filter()函數分別釋放f->exts和f。當內核調用route4_delete()函數進行釋放所有過濾器時,行344,會調用route4_delete_filter_work()函數進行釋放,該函數實現如下:由於handle為0的舊過濾器對應的哈希值依然在route4_head中,因此會對兩個對象進行二次釋放,分別是route4_filer及對應的exts。該漏洞修復補丁如下代碼所示:將判斷條件改成fold是否為空,fold不為空便將其從哈希表中刪除。研究人員提出了一種新的漏洞利用方法,命名為DirtyCred,該利用方法將非特權cred與特權cred進行交換以提升權限,且不需要覆蓋內核堆棧上的任何關鍵數據字段,而是濫用堆內存重寫機制來獲得特權。該利用技術無需泄露內核地址繞過KASLR,且通用性較強,可跨不同的內核和架構,能繞過廣泛採用的內核保護和漏洞利用緩解措施。DirtyCred分為三個步驟,過程如下圖所示:首先,DirtyCred打開一個可寫文件「/tmp/x」,它將在內核中分配一個可寫file對象。通過觸發漏洞,源指針會引用相應緩存中的file對象。然後,DirtyCred嘗試將內容寫入打開的文件「/tmp/x」中。在實際寫入內容之前,內核會檢查當前文件是否有寫權限,該位置是否可寫等。通過檢查後,DirtyCred會繼續這個實際的文件寫入操作,進入第二步。在此步驟中,DirtyCred觸發fs_context對象的釋放點以解除分配file對象,這就使file對象成為一個被釋放的內存點。第三步,DirtyCred打開了一個只讀文件「/etc/passwd」,這觸發了內核為「/etc/passwd」分配file對象。如上圖所示,新分配的file對象接管了被釋放的位置。完成此設置後,DirtyCred將釋放其暫停的寫入操作,而內核將執行實際的內容寫入。由於file對象已經被調換,擱置的內容將被重定向到只讀文件「/etc/passwd」中。假設寫入「/etc/passwd」的內容是「hacker:x:0:0:root:/:/bin/sh」,輕易地注入一個特權賬戶,從而實現權限提升。(1)file slab碎片整理
首先創建10000個「/etc/passwd」的file對象,進行file slab碎片整理,為堆噴做準備。(2)緩存跨越(cross-cache)
當為特定類型的對象創建專用的內存緩存時,該緩存中將只存在該類型的對象,從而防止不同類型的對象相鄰。但是,不同slab cache的slab page可以相鄰。當slab A與slab B相鄰時,slab A末尾的對象與slab B開頭的另一個對象相鄰。因此,攻擊者有可能放置帶有任何類型受害者對象的slab頁在易受攻擊對象的slab page之後,溢出受害者對象來執行攻擊。當一個slab page被釋放給夥伴系統時,它會在稍後的某個時候被重用,因為內存頁面應該被內核回收。cross-cache攻擊的技術是釋放slab page中的所有內存槽,強制釋放slab page。然後再噴射另一種類型的對象分配新的slab page,回收釋放的slab page。如果攻擊成功,被釋放對象的內存將被另一種類型的對象占用。由於file對象在專屬緩存中分配,而漏洞中route4_filter和exts在通用緩存中分配,正常情況下,兩個緩存是隔離的,無法進行常規的對象占坑。作者採用了緩存跨越攻擊解決了這個問題。前文講到route4_filter對象大小為144,在kmalloc-192中分配,exts對象大小為256,在kmalloc-256中分配。file對象大小為256(默認配置情況下),在專屬內存中分配。先通過創建basic過濾器在kmalloc-256 slab中進行內存布局。然後創建route4過濾器,將會創建一個route4_filter漏洞對象和exts對象。最後再進行一部分basic過濾器創建,完成內存布局。關鍵對象內存分布如下圖所示:然後第一次觸發漏洞,釋放route4_filter對象和exts對象。然後再全部釋放basic->exts對象,讓夥伴系統回收kmalloc-256 slab page。由於內核默認開啟slab double free緩解機制,所以這裡要亂序釋放多個basic->exts。(3)堆噴占坑
大量打開一個普通文件data2,進行file對象分配占坑route4->exts。緩存跨越攻擊成功後,然後再次觸發漏洞,第二次釋放route4_filter,跟着釋放route4->exts,將會把file對象非法釋放掉。(4)尋找file對象空洞
二次釋放後,出現了一個file對象空洞,需要找到它。所以此時再次大量打開另一個普通文件uaf,進行file對象堆噴,將會占據這個file對象空洞。然後通過kcmp()系統調用檢查 pid1 和 pid2 標識的兩個進程是否共享文件描述符定位fds[j]。(5)延長時間窗口和寫惡意數據
找到目標file對象的文件描述符fds[j]後,啟動兩個線程,slow_write線程向uaf文件中寫2G數據,用於延長時間窗口。然後write_cmd線程通過fds[j]文件描述符寫惡意數據。(6)再次釋放並堆噴
最後進行close操作,釋放file對象,隨即大量打開「/etc/passwd」文件進行file對象堆噴。堆噴成功後,順利向「/etc/passwsd」寫數據,注入一個特權賬戶完成權限提升。在內核版本5.4.124中,開啟CONFIG_NET_CLS_ACT和CONFIG_NET_SCH_SFQ,可成功復現漏洞。打印調試關鍵數據,如下圖所示:參考鏈接:
[1]https://grsecurity.net/how_autoslab_changes_the_memory_unsafety_game[2]https://www.openwall.com/lists/oss-security/2022/08/09/6[3]https://www.man7.org/linux/man-pages/man7/rtnetlink.7.html[4]https://legacy.netdevconf.info/0.1/papers/Rtnetlink-dump-filtering-in-the-kernel.pdf[5] https://github.com/Markakd/DirtyCred[6] https://github.com/Markakd/CVE-2022-2588[7]https://zplin.me/papers/DirtyCred.pdfADLab成立於1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員,「黑雀攻擊」概念首推者。截止目前,ADLab已通過CVE累計發布安全漏洞近1100個,通過 CNVD/CNNVD累計發布安全漏洞2000餘個,持續保持國際網絡安全領域一流水準。實驗室研究方向涵蓋操作系統與應用系統安全研究、移動智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、雲安全研究。研究成果應用於產品核心技術研究、國家重點科技項目攻關、專業安全服務等。
