張?jiān)茲綖I興,2,杜春來,王忠儒,崔志堅(jiān),宋首友,5
(1.北京郵電大學(xué)網(wǎng)絡(luò)空間安全學(xué)院,北京 100876;2.廣州大學(xué)網(wǎng)絡(luò)空間先進(jìn)技術(shù)研究院,廣東 廣州 510006;3.北方工業(yè)大學(xué)信息學(xué)院,北京 100144;4.中國網(wǎng)絡(luò)空間研究院信息化研究所,北京 100010;5.北京丁??萍加邢薰?,北京 100081)
在數(shù)字經(jīng)濟(jì)背景下,云計(jì)算被寫入《中華人民共和國國民經(jīng)濟(jì)和社會發(fā)展第十四個五年規(guī)劃和2035 年遠(yuǎn)景目標(biāo)綱要》。中國信息通信研究院數(shù)據(jù)顯示,預(yù)計(jì)到2024 年,我國云市場規(guī)模將接近7 800 億元。以容器為代表的云原生技術(shù)蓬勃發(fā)展,為云計(jì)算數(shù)字化賦能提供了重要推動力,并為軟件開發(fā)和系統(tǒng)運(yùn)維帶來了顛覆性的變革。容器技術(shù)[1]是一種輕量級的虛擬化技術(shù),可以實(shí)現(xiàn)應(yīng)用程序在虛擬化隔離環(huán)境中的穩(wěn)定運(yùn)行。在傳統(tǒng)操作系統(tǒng)級別的虛擬化技術(shù)中,每個實(shí)例都需要運(yùn)行客戶端操作系統(tǒng)的完整副本,因此造成了大量的硬件模擬開銷[2]。容器是進(jìn)程級的虛擬化,不需要模擬硬件,相比于傳統(tǒng)虛擬化技術(shù)少了很多開銷,從而可以輕量高效地運(yùn)行應(yīng)用程序。容器編排通過微服務(wù)體系結(jié)構(gòu)模式來實(shí)現(xiàn)業(yè)務(wù)流程自動化,是大規(guī)模容器應(yīng)用的重要工具。容器編排工具(如 Kubernetets(K8S)[3]和 Docker Swarm[4])的出現(xiàn)極大地方便了容器化應(yīng)用程序的部署、擴(kuò)展和管理。目前,容器技術(shù)已經(jīng)廣泛應(yīng)用于大型互聯(lián)網(wǎng)公司的生產(chǎn)環(huán)境和云服務(wù)供應(yīng)商,如Amazon Fargate、Microsoft Azure Kubernetes 等。
容器技術(shù)雖然已經(jīng)在實(shí)際生產(chǎn)環(huán)境被廣泛使用,但存在較多潛在的安全威脅,如容器逃逸、資源隔離失效、鏡像安全威脅、橫向移動攻擊、拒絕服務(wù)攻擊、運(yùn)行環(huán)境未加固等[5]。其中,容器逃逸直接影響了承載容器的底層基礎(chǔ)設(shè)施的安全性和可用性,進(jìn)而對生產(chǎn)環(huán)境造成了安全隱患。具體來說,容器逃逸[6]是指攻擊者利用程序、系統(tǒng)的漏洞或缺陷,突破容器與宿主機(jī)之間的隔離機(jī)制,獲得在宿主機(jī)上的命令執(zhí)行能力。目前,導(dǎo)致容器逃逸的主要因素包括配置不當(dāng)、應(yīng)用程序漏洞和系統(tǒng)內(nèi)核漏洞。相比于前兩者,系統(tǒng)內(nèi)核漏洞導(dǎo)致的逃逸危害更大、威脅更廣。這是因?yàn)槿萜髋c宿主機(jī)共用一個內(nèi)核,攻擊者可以利用內(nèi)核漏洞進(jìn)行攻擊,從而突破隔離“逃逸”至宿主機(jī),對宿主機(jī)和其他容器造成了極大的安全威脅。因此,如何檢測利用內(nèi)核漏洞的容器逃逸具有重要的研究意義。由于Docker 是當(dāng)前使用范圍最廣的容器引擎之一,本文專注于檢測Linux 平臺下利用內(nèi)核漏洞實(shí)現(xiàn)的Docker 容器逃逸。
目前,Linux 平臺下對利用內(nèi)核漏洞實(shí)現(xiàn)容器逃逸的檢測方法主要是基于運(yùn)行時的異常檢測[7-8]。Salamero[7]使用eBPF 實(shí)現(xiàn)了一個進(jìn)程異常行為檢測工具Falco,可通過連續(xù)監(jiān)視Linux 內(nèi)核中的系統(tǒng)調(diào)用來捕捉異常行為。但是Falco 主要基于預(yù)先設(shè)定的規(guī)則來判斷異常行為,不能有效防御未知漏洞的攻擊。Jian 等[8]提出基于容器內(nèi)進(jìn)程所屬命名空間(namespaces)[9]的變化來檢測容器逃逸行為。但該方案存在2 個缺陷:檢測滯后性和檢測失效性。
為了解決現(xiàn)有容器逃逸檢測中漏報(bào)率較高的問題,本文提出了一種異構(gòu)觀測的實(shí)時檢測方法,并實(shí)現(xiàn)了基于異構(gòu)觀測鏈的容器逃逸檢測原型系統(tǒng),簡稱HOC-Detector。首先,通過大量復(fù)現(xiàn)利用內(nèi)核漏洞實(shí)現(xiàn)的容器逃逸攻擊案例,對容器逃逸流程進(jìn)行建模,將相應(yīng)的攻擊案例劃分為直接逃逸和間接逃逸。容器逃逸后獲得root 權(quán)限的逃逸進(jìn)程是容器內(nèi)進(jìn)程的子進(jìn)程則為直接逃逸;反之則為間接逃逸。然后,對已有利用內(nèi)核漏洞的容器逃逸行為進(jìn)行觀測,并總結(jié)、提煉出一系列觀測點(diǎn),包括進(jìn)程的用戶ID(uid)、組ID(gid)、能力(capabilities)[10]、根目錄(root directory)以及namespaces 等。同時提出容器逃逸是由一系列以“權(quán)限提升”為目的的攻擊手段組合而成的。最后,基于捕獲的進(jìn)程行為生成容器進(jìn)程的起源圖,結(jié)合觀測點(diǎn)構(gòu)建容器進(jìn)程逃逸行為的異構(gòu)觀測鏈,以是否出現(xiàn)“權(quán)限提升”為檢測標(biāo)準(zhǔn),對容器進(jìn)程全生命周期進(jìn)行觀測,實(shí)現(xiàn)對利用內(nèi)核漏洞實(shí)施容器逃逸的檢測。本文的主要貢獻(xiàn)總結(jié)如下。
1) 首先調(diào)研并復(fù)現(xiàn)了10 個利用內(nèi)核漏洞(CVE-2016-5195、CVE-2017-7308、CVE-2017-11176、CVE-2017-18344、CVE-2017-1000112、CVE-2018-18955、CVE-2020-14386、CVE-2021-22555、CVE-2022-0185、CVE-2022-0847)的容器逃逸案例,對利用內(nèi)核漏洞的容器逃逸流程進(jìn)行建模,提煉出容器逃逸行為的主要觀測點(diǎn)和基于“權(quán)限提升”的容器逃逸檢測標(biāo)準(zhǔn)。
2) 利用Linux 內(nèi)核模塊實(shí)現(xiàn)了對容器進(jìn)程操作行為的捕獲和進(jìn)程屬性信息的提取,進(jìn)一步生成了進(jìn)程起源圖。
3) 提出異構(gòu)觀測的檢測方法,基于容器進(jìn)程起源圖和觀測點(diǎn)構(gòu)建異構(gòu)觀測鏈,從多個角度對容器進(jìn)程所執(zhí)行的操作進(jìn)行全生命周期的異構(gòu)觀測,檢測是否有權(quán)限提升的情況。
4) 為了驗(yàn)證系統(tǒng)的有效性,選取4 個具有代表性的容器逃逸內(nèi)核漏洞,并與容器逃逸檢測工具NS-Detector 進(jìn)行對比。其中,利用這4 個漏洞實(shí)現(xiàn)的容器逃逸涵蓋了本文總結(jié)出的兩類逃逸方式:直接逃逸和間接逃逸。實(shí)驗(yàn)結(jié)果表明,本文所提方法可以實(shí)時檢測直接和間接逃逸。同時,基于真實(shí)場景的實(shí)驗(yàn)分析表明,本文的系統(tǒng)有較小的性能開銷。
Linux 下的容器技術(shù)基于內(nèi)核中的namespaces[9]實(shí)現(xiàn)隔離控制,基于cgroups[11]實(shí)現(xiàn)資源分配。由于容器進(jìn)程與宿主機(jī)進(jìn)程共享系統(tǒng)內(nèi)核,攻擊者可以在容器內(nèi)利用內(nèi)核漏洞實(shí)現(xiàn)容器逃逸,進(jìn)而對整個物理機(jī)環(huán)境造成危害[6]。為了在逃逸后獲得root 權(quán)限的交互式命令解釋器(shell),攻擊者通常會先提升當(dāng)前進(jìn)程的權(quán)限,因此可以通過動態(tài)實(shí)時監(jiān)控容器內(nèi)進(jìn)程是否有提權(quán)行為來防御逃逸。Lin 等[12]提出的利用內(nèi)核漏洞的提權(quán)一般包含4 個步驟:繞過內(nèi)核地址空間布局隨機(jī)化(KASLR,kernel address space layout randomization)[13]、繞過管理模式訪問保護(hù)(SMAP,supervisor mode access prevention)和管理模式執(zhí)行保護(hù)(SMEP,supervisor mode execution prevention)[14]、覆蓋內(nèi)核函數(shù)指針劫持控制流和調(diào)用內(nèi)核函數(shù)commit_creds()。Lin 等認(rèn)為前3 個步驟很難直接檢測,所以提出通過檢測進(jìn)程是否調(diào)用函數(shù)commit_creds()來阻止容器內(nèi)進(jìn)程的提權(quán)行為。但是,在實(shí)踐中存在一些其他的內(nèi)核提權(quán)方法,并不會通過調(diào)用函數(shù)comm_cred()來進(jìn)行提權(quán)。比如,攻擊者可以利用Linux 內(nèi)核寫時復(fù)制(copy-on-write)中的條件競爭漏洞[15],繞過虛擬動態(tài)共享對象(vDSO,virtual dynamic shared object)[16]對進(jìn)程內(nèi)存權(quán)限的限制,將漏洞利用代碼(shellcode)[17]注入vDSO 實(shí)現(xiàn)提權(quán)并逃逸。
此外,Jian 等[8]提出了一種基于進(jìn)程所屬namespaces狀態(tài)變化的方案NS-Detecto(rnamespaces detector)來檢測Docker 容器的逃逸行為。Jian 等認(rèn)為,當(dāng)攻擊者從容器中逃逸至宿主機(jī)并獲得一個可控的shell 時,該進(jìn)程仍屬于容器中進(jìn)程的子進(jìn)程,但其namespaces 已突破容器與宿主機(jī)間的隔離,屬于宿主機(jī)進(jìn)程的namespaces。該方案主要存在2 個缺陷:一是檢測滯后性,因?yàn)槿萜魈右萃ǔT谧詈笠徊讲艜黄苙amespaces 的隔離;二是檢測失效性,攻擊者在容器內(nèi)利用內(nèi)核漏洞提權(quán)并獲得對宿主機(jī)目錄操作權(quán)限后,可以通過服務(wù)cron 創(chuàng)建定時任務(wù)的方式來獲取一個反彈shell[18],實(shí)現(xiàn)“間接”容器逃逸。此時,獲得的可控shell 與容器中進(jìn)程無直接關(guān)系,該方案不能檢測出此類型的容器逃逸。
另一類常見的檢測容器逃逸的方法是安全容器[19-20]。與普通容器相比,安全容器運(yùn)行在一個獨(dú)立的微型虛擬機(jī)中,擁有完整的操作系統(tǒng)內(nèi)核。安全容器是通過隔離層增強(qiáng)容器的安全性,即在容器和內(nèi)核之間增加隔離層來阻止逃逸。典型的安全容器技術(shù)有Kata[19]和gVisor[20]。安全容器雖然實(shí)現(xiàn)了容器和內(nèi)核的安全隔離,但中間隔離層的引入增加了攻擊面,可能會導(dǎo)致其他未知的風(fēng)險(xiǎn)。
首先,本文討論的容器逃逸檢測技術(shù)主要針對普通容器。其次,Lin 等的工作用于檢測內(nèi)核提權(quán),Jian 等提出的NS-Detector 是目前唯一用于檢測利用內(nèi)核漏洞實(shí)現(xiàn)容器逃逸的工作,與本文研究內(nèi)容直接相關(guān)??傮w來說,當(dāng)前對利用內(nèi)核漏洞實(shí)現(xiàn)容器逃逸行為的檢測方案主要面臨檢測漏報(bào)率較高的問題。在2.3 節(jié)中,本文對利用內(nèi)核實(shí)現(xiàn)的容器逃逸建模后將逃逸行為劃分為直接逃逸和間接逃逸。當(dāng)前的容器逃逸檢測方案僅能檢測直接逃逸,無法有效檢測間接逃逸。本文提出異構(gòu)觀測鏈的方法可以在保證檢測直接逃逸的前提下檢測間接逃逸。
2008 年,Moreau 等[21]提出了開放起源模型(OPM,open provenance model)。OPM 的本質(zhì)是一個包含不同對象間依賴關(guān)系的有向無環(huán)圖,可用于描述某一對象在特定時間階段的所有操作。OPM 示例如圖1 所示,共定義了3 類節(jié)點(diǎn)。
圖1 OPM 示例
1) 狀態(tài)(artifact):用橢圓形表示,代表不可變的狀態(tài),對應(yīng)于計(jì)算機(jī)中的數(shù)據(jù)對象。
2) 過程(process):用矩形表示,代表施加在數(shù)據(jù)對象上的行為,可以產(chǎn)生新的狀態(tài)節(jié)點(diǎn)。
3) 代理(agent):用六邊形表示,用于控制或影響過程的執(zhí)行。
3 類節(jié)點(diǎn)之間的邊代表不同的因果(依賴)關(guān)系:有向邊的頭部是結(jié)果,尾部(箭頭)是原因。如圖1 所示,3 類節(jié)點(diǎn)之間有5 種依賴關(guān)系。
1) used:該依賴關(guān)系代表過程P 的執(zhí)行需要使用狀態(tài)A。
2) wasGeneratedBy:該依賴關(guān)系代表過程P 的執(zhí)行產(chǎn)生了狀態(tài)A。
3) wasTriggeredBy:該依賴關(guān)系代表過程P2的執(zhí)行需要過程P1先執(zhí)行。一般P1是父進(jìn)程,P2是子進(jìn)程。
4) wasControlledBy:該依賴關(guān)系代表過程P 的開始和結(jié)束都由代理Ag 控制。
5) wasDerivedFrom:該依賴關(guān)系代表狀態(tài)A2描述的數(shù)據(jù)對象依賴于狀態(tài)A1所代表的數(shù)據(jù)對象。
在OPM 中,一個過程可能使用或產(chǎn)生多個狀態(tài),也有可能被多個代理控制,所以需要準(zhǔn)確地識別這些狀態(tài)集和代理集。OPM 引入角色的概念,如圖1 所示,每條邊上標(biāo)注了角色(R),并且給每個狀態(tài)和代理規(guī)定唯一標(biāo)識的角色。
在Linux 系統(tǒng)中,根據(jù)特權(quán)級別不同,將進(jìn)程運(yùn)行狀態(tài)劃分為用戶態(tài)和內(nèi)核態(tài)。應(yīng)用程序一般運(yùn)行在用戶態(tài),當(dāng)其執(zhí)行系統(tǒng)調(diào)用或發(fā)生中斷而陷入內(nèi)核中執(zhí)行時,就稱進(jìn)程處于內(nèi)核態(tài)。當(dāng)進(jìn)程在內(nèi)核態(tài)時,可以直接訪問操作系統(tǒng)內(nèi)核中與進(jìn)程屬性相關(guān)的數(shù)據(jù)結(jié)構(gòu)。因此,攻擊者可以利用內(nèi)核漏洞劫持控制流來執(zhí)行惡意代碼修改容器進(jìn)程與權(quán)限相關(guān)的數(shù)據(jù)結(jié)構(gòu),從而提升容器進(jìn)程的權(quán)限,進(jìn)一步實(shí)現(xiàn)逃逸。內(nèi)核中與容器逃逸相關(guān)的重要數(shù)據(jù)結(jié)構(gòu)如圖2 所示。
圖2 內(nèi)核中與容器逃逸相關(guān)的重要數(shù)據(jù)結(jié)構(gòu)
1) task_struct:內(nèi)核中與進(jìn)程管理和控制相關(guān)最重要的數(shù)據(jù)結(jié)構(gòu)。該結(jié)構(gòu)體伴隨進(jìn)程整個生命周期,記錄進(jìn)程的當(dāng)前狀態(tài)以及控制進(jìn)程運(yùn)行的全部信息(打開的文件、信號量、進(jìn)程狀態(tài)、地址空間等)。
2) cred:系統(tǒng)通過該結(jié)構(gòu)體控制進(jìn)程對其他資源的操作權(quán)限判定。當(dāng)進(jìn)程操作消息隊(duì)列、共享內(nèi)存和信息量等數(shù)據(jù)對象時,系統(tǒng)需要對進(jìn)程的euid、egid 進(jìn)行檢查;當(dāng)進(jìn)程執(zhí)行文件相關(guān)的操作時,系統(tǒng)需要對進(jìn)程的fsuid、fsgid 進(jìn)行檢查。同時,Linux使用能力(capability)機(jī)制,以細(xì)粒度方式控制普通進(jìn)程能否執(zhí)行“特權(quán)”操作。例如,進(jìn)程要掛載(mount)一個文件系統(tǒng),那么進(jìn)程需要有對應(yīng)的capability,即CAP_SYS_ADMIN。
3) fs_struct:用于描述進(jìn)程的文件系統(tǒng)信息。結(jié)構(gòu)體中的root 和pwd 分別代表進(jìn)程的根目錄和當(dāng)前工作目錄。
4) nsproxy:用于描述進(jìn)程各個namespaces 的狀態(tài)。其中,pid namespaces 為進(jìn)程提供了一個具有獨(dú)立進(jìn)程ID 的運(yùn)行環(huán)境。在每一個pid namespaces 中,進(jìn)程的pid 從1 開始編號,且和其他pid namespaces 中的pid 互不影響。
利用內(nèi)核漏洞實(shí)現(xiàn)容器逃逸一般可分為兩步,攻擊者首先利用內(nèi)核漏洞劫持內(nèi)核的控制流,然后執(zhí)行惡意代碼實(shí)現(xiàn)容器逃逸。在復(fù)現(xiàn)并觀測大量利用內(nèi)核漏洞的容器逃逸后,本文將逃逸行為建模為如圖3 所示的流程。根據(jù)逃逸后獲取的root 權(quán)限進(jìn)程(shell)是否為容器中進(jìn)程的子進(jìn)程,將容器逃逸分為兩類:直接逃逸和間接逃逸。
圖3 利用內(nèi)核漏洞的直接、間接逃逸流程
2.3.1 直接逃逸
在直接逃逸中,攻擊者在劫持內(nèi)核控制流之后一般會提升當(dāng)前容器進(jìn)程的權(quán)限,確保逃逸后可以獲得root 權(quán)限的進(jìn)程。如圖3 所示,攻擊者首先觸發(fā)內(nèi)核漏洞,通過覆蓋內(nèi)核函數(shù)指針,將內(nèi)核控制流劫持到用戶態(tài)。在 Payload 中調(diào)用內(nèi)核函數(shù)commit_creds(prepare_kernel_cred(0)) 將當(dāng)前進(jìn)程(cur)的cred 替換為root 權(quán)限的cred。然后,執(zhí)行內(nèi)核函數(shù)switch_task_namespacess(cur,INIT_NSPROXY) 突破系統(tǒng)對容器內(nèi)進(jìn)程的Namespaces 隔離。最后,cur 進(jìn)程創(chuàng)建一個root 權(quán)限的逃逸進(jìn)程,實(shí)現(xiàn)直接容器逃逸。
2.3.2 間接逃逸
間接逃逸與直接逃逸最大的區(qū)別是不需要容器內(nèi)的進(jìn)程突破namespaces 的隔離也能獲得root權(quán)限的逃逸進(jìn)程。如圖3 中的3.1) 所示,攻擊者劫持控制流后,在 Payload 中依次執(zhí)行內(nèi)核函數(shù)commit_creds(prepare_kernel_cred(0))和copy_fs_struct(init+TASK_FS_OFFSET),分別提升容器內(nèi)當(dāng)前進(jìn)程的權(quán)限和將進(jìn)程根目錄切換為宿主機(jī)上1 號進(jìn)程的根目錄。此時,攻擊者可以通過改寫宿主機(jī)文件的方式實(shí)現(xiàn)逃逸。例如,容器中的進(jìn)程在宿主機(jī)/var/spool/cron/crontabs/目錄下新建root 文件,向其中寫入執(zhí)行反彈shell 的命令,當(dāng)宿主機(jī)的root 權(quán)限的進(jìn)程cron 執(zhí)行該文件中的命令后獲得一個root權(quán)限的交互式shell。最后得到的root 權(quán)限進(jìn)程與容器中的進(jìn)程并無直接繼承關(guān)系,實(shí)現(xiàn)間接容器逃逸。另一種實(shí)現(xiàn)間接逃逸的方式是在劫持內(nèi)核控制流后不調(diào)用內(nèi)核函數(shù),而是用shellcode 覆蓋內(nèi)核中的函數(shù)。如圖3 中的3.2) 所示,攻擊者覆蓋內(nèi)核中的clock_gettime()函數(shù),隨后宿主機(jī)上root 權(quán)限的進(jìn)程調(diào)用clock_gettime()函數(shù)獲取時間時會開啟一個root 權(quán)限的shell,實(shí)現(xiàn)間接逃逸。
因此,利用內(nèi)核漏洞實(shí)現(xiàn)的容器逃逸在其逃逸過程中所執(zhí)行的操作一般會表現(xiàn)出“權(quán)限提升”的特點(diǎn)。從攻防雙方來看,攻擊者可以憑此探索新的容器逃逸方法,一旦有新的內(nèi)核漏洞,就可以考慮是否可用于容器逃逸;防守者則可以針對此特征來檢測容器進(jìn)程生命周期中有無權(quán)限提升發(fā)生,以此來防御利用內(nèi)核漏洞實(shí)現(xiàn)的容器逃逸。
進(jìn)程執(zhí)行不同操作前后的屬性變化能體現(xiàn)出權(quán)限提升,而進(jìn)程屬性本質(zhì)上由內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)來控制。因此,為了觀測進(jìn)程生命周期中是否存在權(quán)限提升,本文基于內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)選取觀測點(diǎn)列表S為[fs_root,cap_permited,uid,gid,mnt_ns,pid_ns,net_ns],其中,fs_root 表示進(jìn)程的根目錄,cap_permited 表示進(jìn)程所能夠擁有特權(quán)的上限,uid和gid 分別表示進(jìn)程所屬的用戶ID 和組ID,觀測點(diǎn)列表中剩余部分均與進(jìn)程的namespaces 相關(guān)。
為檢測上述2 類容器逃逸,本文基于進(jìn)程的操作構(gòu)建進(jìn)程起源圖,以此來描述進(jìn)程行為之間的關(guān)系。隨后,結(jié)合進(jìn)程起源圖和觀測點(diǎn)構(gòu)建異構(gòu)觀測鏈,對進(jìn)程完整生命周期進(jìn)行監(jiān)控,即檢測進(jìn)程行為引發(fā)的觀測點(diǎn)屬性變化情況,若存在權(quán)限提升的屬性變化,則判定進(jìn)程正在進(jìn)行逃逸。
本文所提出的基于異構(gòu)觀測鏈的容器進(jìn)程實(shí)時檢測系統(tǒng)旨在通過對容器內(nèi)的進(jìn)程進(jìn)行全生命周期的監(jiān)控來檢測是否存在利用內(nèi)核漏洞的容器逃逸行為。為此,一方面需要確保監(jiān)控和檢測過程的實(shí)時性,在不能引入過高的性能開銷的同時也需要保證系統(tǒng)的穩(wěn)定運(yùn)行;另一方面需要保證檢測的有效性。因此,系統(tǒng)的設(shè)計(jì)目標(biāo)包括以下4 個方面。
1) 保證捕捉進(jìn)程行為和提取進(jìn)程屬性信息的實(shí)時性?;诖艘?,HOC-Detector 需要及時捕獲進(jìn)程的操作,然后提取其行為、屬性信息。
2) 盡量減小逃逸檢測時延。為減小檢測逃逸的時延,除需要快速提取進(jìn)程的行為、屬性信息外,還需要縮小逃逸檢測的范圍。
3) 引入較小的性能開銷。在確保進(jìn)程信息提取的實(shí)時性、檢測低時延的前提下,盡量減小HOC-Detector 對系統(tǒng)造成運(yùn)行時開銷。
4) 能夠防御未知威脅。對于使用未知內(nèi)核漏洞進(jìn)行容器逃逸的行為,HOC-Detector 要能準(zhǔn)確地捕捉,以確保逃逸檢測的有效性。
進(jìn)程的動態(tài)行為可由其所執(zhí)行的一系列操作來刻畫。當(dāng)進(jìn)程進(jìn)行容器逃逸時,往往會執(zhí)行一些特別的操作來繞過系統(tǒng)中的安全機(jī)制,而操作前后進(jìn)程的屬性變化可以反映出這些操作是否合法。為此,本文借助OPM 將系統(tǒng)中進(jìn)程執(zhí)行的所有操作抽象為進(jìn)程起源圖,從起源圖上提取容器內(nèi)進(jìn)程的觀測鏈。如圖4 所示,該觀測鏈由進(jìn)程、文件等對象以及表征其相關(guān)操作的有向依賴邊構(gòu)成。
圖4 基于進(jìn)程行為起源圖的異構(gòu)觀測鏈
本文將觀測鏈中依賴邊的尾部(箭頭)節(jié)點(diǎn)稱為主體,用T(tail)表示;邊的頭部節(jié)點(diǎn)稱為客體,用H(head)表示;主體和客體均可以是進(jìn)程、文件等。進(jìn)程用Ppid表示,其中pid 為進(jìn)程號;文件等其他資源用Aname表示,其中name 為標(biāo)識符。主體與客體間的行為用Oop表示,其中op∈{fork,clone,read,write,…};主體對客體之間的操作關(guān)系用TOopH 表示。
如圖4 所示,節(jié)點(diǎn)代表containerd-shim 進(jìn)程,在T2時刻,容器中的init 進(jìn)程 P1021復(fù)制了一個子進(jìn)程 P1022,該操作可以表示為 P1021OforkP1022。在T3時刻,P1022運(yùn)行漏洞利用程序后創(chuàng)建了一個新的進(jìn)程P1023,該操作可以表示為 P1022OforkP1023。在T4時刻,該進(jìn)程讀取了宿主機(jī)中/etc/shadow 文件中的內(nèi)容P1023OreadAshadow。從圖 4 中得到的觀測鏈為(P1021OforkP1022) ∧(P1022OforkP1023) ∧(P1023OreadAshadow)。其中,符號∧用來連接觀測鏈上進(jìn)程的操作。
在得到容器內(nèi)進(jìn)程的觀測鏈后,本文基于2.3 節(jié)中提出的觀測點(diǎn)列表S,在進(jìn)程生命周期中不同的時間節(jié)點(diǎn)對進(jìn)程的操作進(jìn)行異構(gòu)觀測。采用3.5 節(jié)中提出的檢測標(biāo)準(zhǔn)來評估進(jìn)程操作是否存在權(quán)限提升,以此來判斷是否發(fā)生了逃逸行為。使用異構(gòu)觀測鏈的方法來檢測容器逃逸有2 個明顯的優(yōu)勢,一是異構(gòu)觀測鏈涵蓋進(jìn)程在不同時間點(diǎn)所執(zhí)行的操作,可以在早期就檢測到進(jìn)程的逃逸行為,顯著降低逃逸檢測的時延;二是異構(gòu)觀測鏈包含進(jìn)程生命周期中所有相關(guān)的操作,可以對進(jìn)程實(shí)現(xiàn)全面的監(jiān)控,顯著降低逃逸檢測的漏報(bào)。
HOC-Detector 總體架構(gòu)如圖5 所示,由進(jìn)程日志獲取模塊(AuditLogger)、進(jìn)程起源圖生成模塊(PPGGenerator )、異構(gòu)觀測鏈構(gòu)建模塊(HOCBuilder)、攻擊檢測模塊(AttackDetector)和預(yù)先配置的觀測點(diǎn)(ObservationPoints)五部分組成。在HOC-Detector 運(yùn)行過程中,AuditLogger 在系統(tǒng)內(nèi)核中監(jiān)視進(jìn)程,并將其行為、屬性信息保存在宿主機(jī)的日志文件中;與此同時,PPGGenerator 從日志文件中讀取內(nèi)容,采取基于因果關(guān)系的方法構(gòu)建進(jìn)程的起源圖,并將其傳遞給 HOCBuilder;HOCBuilder 基于起源圖并結(jié)合用戶提供的觀測點(diǎn)構(gòu)建異構(gòu)觀測鏈,AttackDetector 對異構(gòu)觀測鏈進(jìn)行分析,檢測容器進(jìn)程是否存在逃逸行為。
為了對基于內(nèi)核的容器逃逸有更清晰的了解,本文在2.3 節(jié)中通過對內(nèi)核漏洞導(dǎo)致的容器逃逸進(jìn)行大量復(fù)現(xiàn)后,對其逃逸流程進(jìn)行建模。同時,從是否能很好地表征權(quán)限提升的角度選取進(jìn)程的屬性信息作為觀測點(diǎn)。如圖5 中的ObservationPoints所示,觀測點(diǎn)列表為[S[1],S[2],…,S[m]]。同時,為了增加對未知威脅的防御能力,可以動態(tài)調(diào)整HOC-Detector 中的觀測點(diǎn)。
圖5 HOC-Dectector 總體架構(gòu)
在HOC-Detector 的運(yùn)行過程中,AuditLogger需要持續(xù)實(shí)時地捕捉進(jìn)程的行為和記錄屬性的信息。因此,本文使用內(nèi)核編程的方式通過動態(tài)可加載的內(nèi)核模塊在內(nèi)核中截取進(jìn)程所執(zhí)行的系統(tǒng)調(diào)用,記錄其系統(tǒng)調(diào)用名、參數(shù)和返回值等信息,實(shí)現(xiàn)對進(jìn)程行為的實(shí)時監(jiān)控。HOC-Detector 的設(shè)計(jì)目標(biāo)是檢測利用內(nèi)核漏洞的容器逃逸,而容器本質(zhì)上是宿主機(jī)上使用namespaces 和cgroups 實(shí)現(xiàn)資源隔離的普通進(jìn)程。宿主機(jī)上的進(jìn)程數(shù)目龐大,為了實(shí)現(xiàn)容器逃逸檢測的實(shí)時性,本文需要重點(diǎn)關(guān)注容器相關(guān)的進(jìn)程。因此,需要識別出容器內(nèi)外進(jìn)程的邊界,減輕后續(xù)異構(gòu)觀測鏈構(gòu)建和攻擊檢測的復(fù)雜度。PPGGenerator 對記錄的進(jìn)程日志信息進(jìn)行語義解析,構(gòu)建進(jìn)程起源圖。隨后,HOCBuilder 結(jié)合起源圖和觀測點(diǎn)構(gòu)建異構(gòu)觀測鏈。最后,AttackDetector 基于異構(gòu)觀測鏈檢測進(jìn)程的整個生命周期中是否存在權(quán)限提升的情況,判斷是否存在容器逃逸行為。如圖5 中的AttackDetector 所示,該模塊對進(jìn)程操作前后的每個觀測點(diǎn)Si進(jìn)行對比,判斷其是否存在權(quán)限提升,ai表示檢測結(jié)果。當(dāng)超過半數(shù)的觀測點(diǎn)存在權(quán)限提升時,本文判定當(dāng)前進(jìn)程存在逃逸行為。
Linux 系統(tǒng)中的/proc 目錄是一種虛擬文件系統(tǒng),該目錄下的文件描述了內(nèi)核當(dāng)前的運(yùn)行狀態(tài),可以在用戶態(tài)通過讀取/proc/pid 目錄下文件的內(nèi)容來查看當(dāng)前正在運(yùn)行進(jìn)程的信息。該方法主要面臨2 個挑戰(zhàn):一是系統(tǒng)中的進(jìn)程數(shù)目龐大,頻繁讀取/proc 目錄下文件中的內(nèi)容開銷很大;二是攻擊者在實(shí)施攻擊的過程中可能會隱藏一些進(jìn)程,導(dǎo)致不能在/proc 目錄下找到對應(yīng)進(jìn)程的信息。因此,本文通過內(nèi)核模塊編程的方式在內(nèi)核態(tài)提取與進(jìn)程相關(guān)的關(guān)鍵屬性信息,并將其保存到宿主機(jī)的日志文件中。
本文所選取的觀測點(diǎn)可以通過進(jìn)程描述符task_struct 獲取,所以首先需要得到進(jìn)程的task_struct結(jié)構(gòu)體,然后根據(jù)其結(jié)構(gòu)體成員提取進(jìn)程觀測點(diǎn)信息。該功能由HOC-Detector 中的AuditLogger 模塊實(shí)現(xiàn),采用Linux Audit[22]技術(shù)在內(nèi)核中捕捉系統(tǒng)中所有進(jìn)程的各種操作行為,如系統(tǒng)調(diào)用、文件讀寫、執(zhí)行命令等。其具體實(shí)現(xiàn)流程如下。
1) 在內(nèi)核態(tài)截取與新進(jìn)程創(chuàng)建相關(guān)的系統(tǒng)調(diào)用,例如clone、fork 和vfork。當(dāng)進(jìn)程執(zhí)行這些系統(tǒng)調(diào)用后,將其返回值傳給/include/linux/pid.h 中的函數(shù)find_vpid()以獲取新進(jìn)程的struct pid 結(jié)構(gòu)體指針,它指向的結(jié)構(gòu)體保存了新進(jìn)程的進(jìn)程描述符信息。
2) 把步驟1)中指向struct pid 的指針作為函數(shù)pid_task()的參數(shù),得到進(jìn)程的task_struct 結(jié)構(gòu)體。
3) 利用__task_cred()讀取task_struct 中的指針cred 所指向的結(jié)構(gòu)體,從中提取進(jìn)程的用戶和組權(quán)限uid、gid、euid、fsuid 等。同時,提取進(jìn)程的細(xì)粒度權(quán)限信息cap_permitted。
4) 通過task_struct中的指針fs指向的結(jié)構(gòu)體獲得進(jìn)程根目錄root,然后根據(jù)root 中的dentry 得到根目錄項(xiàng)中表示所有子目錄的鏈表指針d_subdirs。最后,通過遍歷d_subdirs 得到根目錄下所有子目錄或子文件的名字。
5) 通過task_struct 中的指針nsproxy 指向的結(jié)構(gòu)體獲得進(jìn)程namespaces 的信息。
6) 系統(tǒng)中的每個進(jìn)程可能屬于多個不同的namespaces,通過結(jié)構(gòu)體struct pid 中的numbers 數(shù)組來獲取進(jìn)程在不同pid namespaces 中的進(jìn)程編號。在容器逃逸場景下,pid 表示進(jìn)程在宿主機(jī)環(huán)境下的進(jìn)程號(全局進(jìn)程號),vpid 表示容器中進(jìn)程在其隔離的pid namespaces 中的進(jìn)程號(局部進(jìn)程號)。
如圖6 所示,runc 進(jìn)程(父進(jìn)程,pid=2556)正在執(zhí)行容器初始化,它通過執(zhí)行系統(tǒng)調(diào)用號為56的clone 來創(chuàng)建一個子進(jìn)程(pid=2557)。本文將進(jìn)程編號2557 作為參數(shù)依次執(zhí)行上面描述的操作來提取新進(jìn)程的信息。為了方便對比在新進(jìn)程創(chuàng)建時有無權(quán)限提升的情況發(fā)生,本文也會提取父進(jìn)程的相關(guān)屬性信息。如圖6 所示,子進(jìn)程的命名空間與父進(jìn)程的命名空間(mnt_ns,pid_ns,net_ns)不一致,并且子進(jìn)程的局部進(jìn)程編號為1(vpid=1),說明該子進(jìn)程是新啟動的容器中的init 進(jìn)程。為了方便表示進(jìn)程的根目錄,本文將容器內(nèi)的進(jìn)程根目錄root_path 標(biāo)識為container,容器外進(jìn)程標(biāo)識為host。
圖6 內(nèi)核模塊提取的進(jìn)程觀測點(diǎn)信息
宿主機(jī)操作系統(tǒng)使用Linux 中的Namespaces技術(shù)來限制容器進(jìn)程可見的資源,使運(yùn)行在同一宿主機(jī)下不同容器中的程序之間互不影響。使用cgroups 技術(shù)來限制進(jìn)程可使用的資源,確保宿主機(jī)上所有容器公平使用宿主機(jī)上的資源,如CPU、內(nèi)存、磁盤和網(wǎng)絡(luò)等。所以,容器僅是宿主機(jī)上的一個特殊的進(jìn)程,與其他進(jìn)程相比沒有本質(zhì)的區(qū)別。但Linux Audit 在記錄進(jìn)程信息時并不會區(qū)分當(dāng)前進(jìn)程是普通進(jìn)程還是容器進(jìn)程,而容器內(nèi)進(jìn)程的行為是本文檢測容器逃逸的關(guān)鍵。為了優(yōu)化異構(gòu)觀測鏈的構(gòu)建以及提高容器逃逸的檢測效率,本文提出建立對容器內(nèi)外進(jìn)程邊界的識別,該過程僅需關(guān)注與容器內(nèi)進(jìn)程及其行為相關(guān)的進(jìn)程。
通過分析Docker 容器的初始化流程可以發(fā)現(xiàn),容器的啟動有固定的模式,基于此來識別容器內(nèi)外進(jìn)程的邊界。容器的初始化以執(zhí)行系統(tǒng)調(diào)用unshare創(chuàng)建新的命名空間系統(tǒng)開始,以執(zhí)行系統(tǒng)調(diào)用execve 在容器內(nèi)創(chuàng)建新的init 進(jìn)程結(jié)束。本文以Docker 容器的初始化為例,如圖7 所示,其初始化流程介紹如下。
圖7 Docker 容器初始化流程
1) dockerd 通過gPRC 接口向容器運(yùn)行時管理引擎containerd 發(fā)送指令創(chuàng)建容器,隨后containerd(pid=1405)會啟動一個進(jìn)程 containerd-shim(pid=2522),由它負(fù)責(zé)創(chuàng)建一個新的容器。
2) 該進(jìn)程(pid=2522)會復(fù)制出一系列的containerd-shim 進(jìn)程,其中一個 containerd-shim(pid=2532)會創(chuàng)建新的runC 進(jìn)程來完成容器初始化操作。
3) 進(jìn)程 runc(pid=2540)會復(fù)制出子進(jìn)程runc:[0:PARENT],該進(jìn)程的子進(jìn)程runc:[1:CHILD]會執(zhí)行系統(tǒng)調(diào)用unshare 創(chuàng)建新的命名空間,標(biāo)志著開始進(jìn)行實(shí)際的容器初始化。
4) 進(jìn)程 runc:[1:CHILD]會復(fù)制一些子進(jìn)程runc:[2:INIT]來完成一些特定的初始化任務(wù),包括設(shè)置/proc 和/rootfs 等。
5) 最后,進(jìn)程runc:[1:CHILD]將復(fù)制一個子進(jìn)程(pid=2563),該子進(jìn)程即容器內(nèi)的init 進(jìn)程,它將執(zhí)行系統(tǒng)調(diào)用execve 來運(yùn)行容器默認(rèn)的啟動程序(如bash)。
HOC-Dectector 中的PPGGenerator 模塊基于系統(tǒng)收集的進(jìn)程行為信息構(gòu)建進(jìn)程起源圖,本文使用上述容器初始化模式在起源圖上標(biāo)記容器內(nèi)外進(jìn)程的邊界,僅關(guān)注容器內(nèi)的進(jìn)程,減輕后續(xù)構(gòu)建觀測鏈的工作量。
觀測鏈的構(gòu)建依賴于能夠?qū)⑦M(jìn)程生命周期內(nèi)所有行為之間的依賴關(guān)系抽象為有向的進(jìn)程起源圖。HOC-Detector 中的HOCBuilder 模塊利用開源框架SPADE[23]來提取進(jìn)程起源圖,利用3.4 節(jié)中描述的方法識別容器內(nèi)外進(jìn)程的邊界和負(fù)責(zé)啟動容器的containerd-shim 進(jìn)程。然后,以啟動容器的containerd-shim 進(jìn)程為根節(jié)點(diǎn),以廣度優(yōu)先的方式遍歷起源圖,直到覆蓋所有子節(jié)點(diǎn)或者達(dá)到最大深度,最終從完整的起源圖中提取出與容器進(jìn)程相關(guān)的子圖。如圖4 所示,本文將從containerd-shim 進(jìn)程的節(jié)點(diǎn)到葉節(jié)點(diǎn)的一個節(jié)點(diǎn)序列稱為觀測鏈。
如2.3 節(jié)所述,本文選取的觀測點(diǎn)列表為S。本文在AttackDetector 中將主體T 和客體H 之間單個觀測點(diǎn)的檢測標(biāo)準(zhǔn)Δ定義為
其中,m為本文選取的觀測點(diǎn)列表S的長度,如2.3 節(jié)所述,m=7。冗余技術(shù)在軟件系統(tǒng)中可以極大提升系統(tǒng)的容錯能力,從而提高系統(tǒng)的穩(wěn)定性。本文使用具有冗余能力的多數(shù)表決算法[24]來判定當(dāng)前操作是否存在權(quán)限提升。其基本思想是如果有超過半數(shù)觀測點(diǎn)的檢測標(biāo)準(zhǔn)Δ值為1,則判定為當(dāng)前主客體間的操作存在權(quán)限提升的行為,即如果(當(dāng)m=7時,F(xiàn)AC≥ 4)則判定當(dāng)前的主客體間的操作存在權(quán)限提升,否則為正常操作。進(jìn)程的觀測鏈由多條邊組成,只要有一條邊(主客體間的操作)存在權(quán)限提升,本文就認(rèn)為該進(jìn)程發(fā)生容器逃逸。
為了驗(yàn)證HOC-Dectector 在防御利用內(nèi)核漏洞的容器逃逸攻擊時的有效性,本文選取了4 個影響較大的、可通過不同方式實(shí)現(xiàn)容器逃逸的CVE 內(nèi)核漏洞。本文的實(shí)驗(yàn)環(huán)境主要由物理機(jī)和容器引擎Docker 組成,物理機(jī)的配置為Intel(R) Core(TM)i7-9750H,4 核處理器,10 GB 內(nèi)存,200 GB 硬盤,具體的CVE 信息和系統(tǒng)實(shí)驗(yàn)環(huán)境如表1 所示。本文選擇的CVE包括CVE-2017-7308、CVE-2017-18344、CVE-2017-1000112 和CVE-2016-5195。第一個CVE 可通過調(diào)用內(nèi)核函數(shù)的方式實(shí)現(xiàn)直接容器逃逸;第二個和第三個CVE 一起使用,可通過調(diào)用內(nèi)核函數(shù)實(shí)現(xiàn)間接容器逃逸;最后一個CVE 可通過不調(diào)用內(nèi)核函數(shù)的方式實(shí)現(xiàn)間接容器逃逸。
為檢驗(yàn)HOC-Detector 的有效性,本文復(fù)現(xiàn)了Jian 等提出的方法 NS-Detector 并與之對比。NS-Detector 通過監(jiān)測進(jìn)程所屬namespaces 的狀態(tài)變化來檢測針對Docker 容器的逃逸攻擊。其主要思想是攻擊者實(shí)現(xiàn)容器逃逸攻擊后獲得的root 權(quán)限逃逸進(jìn)程仍屬于容器內(nèi)進(jìn)程的子進(jìn)程,但其所屬Namespaces 已脫離容器的限制。本文將NS-Detector和HOC-Detector 在前述3 個容器逃逸的CVE 實(shí)驗(yàn)中進(jìn)行對比,實(shí)驗(yàn)結(jié)果如表 1 所示。其中,HOC-Detector 能檢測出所有的容器逃逸,準(zhǔn)確率達(dá)到100%,而NS-Detector 的準(zhǔn)確率約為33%。
表1 容器逃逸檢測的內(nèi)核漏洞信息及實(shí)驗(yàn)環(huán)境和結(jié)果
AF_PACKET 是在Linux 平臺下的一種套接字(socket),用于在設(shè)備驅(qū)動層發(fā)送或者接收數(shù)據(jù)包。進(jìn)程可以使用send 和recv 這2 個系統(tǒng)調(diào)用在數(shù)據(jù)包套接字上發(fā)送和接收數(shù)據(jù)包。為了提升效率,套接字提供了一個環(huán)形緩沖區(qū)(ring buffer),能夠使數(shù)據(jù)包的發(fā)送和接收更為高效,且這個環(huán)形緩沖區(qū)可以在內(nèi)核態(tài)和用戶態(tài)之間共享。在利用套接字收發(fā)數(shù)據(jù)時,每個數(shù)據(jù)包會存放在一個單獨(dú)的幀(frame)中,多個幀會被分組形成內(nèi)存塊(block)。CVE-2017-7308 漏洞存在于內(nèi)核處理版本為TPACKET_V3 的環(huán)形緩沖區(qū)的代碼中,由于內(nèi)核在判斷接收到數(shù)據(jù)的長度時存在整數(shù)溢出,使其通過長度的安全性檢查而導(dǎo)致堆溢出漏洞。攻擊者可以通過溢出控制函數(shù)指針來劫持內(nèi)核的控制流。利用該內(nèi)核漏洞進(jìn)一步實(shí)現(xiàn)容器逃逸的攻擊流程如下。
1) 通過/proc/kallsyms 或syslog 的方式泄露內(nèi)核函數(shù)地址來獲得內(nèi)核的加載基址,繞過內(nèi)核KASLR 保護(hù)機(jī)制。
2) 構(gòu)造堆布局觸發(fā)內(nèi)核處理環(huán)形緩沖區(qū)代碼packet_set_ring()中的堆溢出漏洞。
3) 覆蓋packet_socket 結(jié)構(gòu)體中的retire_blk_timer 字段,使該字段中的函數(shù)指針func 指向native_write_cr4()。同時,覆蓋該字段中的data 為函數(shù)native_write_cr4()的參數(shù)。若計(jì)時器超時,執(zhí)行native_write_cr4()來禁用SMEP 和SMAP。
4) 以同樣的方式構(gòu)造堆溢出,覆蓋packet_sock中的xmit 字段,使其指向攻擊載荷函數(shù)get_root_payload()。
5) 在 get_root_payload()函數(shù)中首先執(zhí)行commit_creds()來提升容器內(nèi)當(dāng)前進(jìn)程的權(quán)限;然后,執(zhí)行switch_task_namespaces()來切換容器內(nèi)已提權(quán)進(jìn)程的namespaces,實(shí)現(xiàn)逃逸。由表1 可知,HOC-Detector 和NS-Detector 均能檢測到利用漏洞CVE-2017-7308 實(shí)現(xiàn)的逃逸行為。HOC-Detector 對利用漏洞CVE-2017-7308實(shí)現(xiàn)的容器逃逸過程進(jìn)行監(jiān)測所獲取的觀測鏈如圖8 所示。攻擊者通過運(yùn)行逃逸攻擊程序exploit 獲得一個root 權(quán)限的進(jìn)程(pid=3046)。其中,進(jìn)程(pid=3045)執(zhí)行系統(tǒng)調(diào)用clone 產(chǎn)生新進(jìn)程(pid=3046)的操作存在權(quán)限提升。漏洞CVE-2017-7308 權(quán)限提升操作處的觀測點(diǎn)屬性變化如表2 所示。其中,容器內(nèi)的進(jìn)程(主體T,pid=3045)復(fù)制出的子進(jìn)程(客體H,pid=3046)在所有觀測點(diǎn)的值均與宿主機(jī)上1 號進(jìn)程的值相等()=1,0 ≤i≤ 6),即FAC=7,說明出現(xiàn)了權(quán)限提升,HOC-Detector 可以檢測出利用該內(nèi)核漏洞實(shí)現(xiàn)的直接逃逸行為。由于獲得的逃逸進(jìn)程(pid=3046)是容器中進(jìn)程(pid=3045)的子進(jìn)程,且兩者的namespaces 不一致,NS-Detector 也能檢測到該逃逸行為。
表2 漏洞CVE-2017-7308 權(quán)限提升操作處的觀測點(diǎn)屬性變化
圖8 HOC-Detector 對利用漏洞CVE-2017-7308 實(shí)現(xiàn)的容器逃逸過程進(jìn)行監(jiān)測所獲取的觀測鏈
CVE-2017-18344 存在于Linux 內(nèi)核4.14.8 之前的版本中,在系統(tǒng)調(diào)用 timer_create 的實(shí)現(xiàn)kernel/time/posix-timers.c 中沒有正確驗(yàn)證結(jié)構(gòu)體sigevent 中的字段sigev_notify,存在整型溢出漏洞。當(dāng)用戶讀取/proc/pid/timers 中的內(nèi)容時,可以利用該漏洞在posix-timers.c 的函數(shù)show_timer()中實(shí)現(xiàn)越界訪問,進(jìn)一步使用戶態(tài)程序可以讀取內(nèi)核中任意地址的內(nèi)容。CVE-2017-1000112 存在于Linux 內(nèi)核 4.13.9 之前的版本中,該漏洞位于/net/ipv4/ip_output.c 中的__ip_append_data(),漏洞形成的原因是內(nèi)核通過標(biāo)志SO_NO_CHECK 來判斷在處理數(shù)據(jù)包時是使用UFO 機(jī)制還是non-UFO機(jī)制(UFO 機(jī)制是指用網(wǎng)卡輔助進(jìn)行報(bào)文分片,用戶層協(xié)議不進(jìn)行分片;non-UFO 機(jī)制是指在用戶層進(jìn)行報(bào)文分片)。具體來說,第一次調(diào)用send()函數(shù)發(fā)送數(shù)據(jù)包時,本文可以發(fā)送一個大小超過MTU的數(shù)據(jù)包,這將執(zhí)行UFO 的處理邏輯。在第二次調(diào)用 send()發(fā)送數(shù)據(jù)包之前,先執(zhí)行系統(tǒng)調(diào)用setsockopt 來設(shè)置 SO_NO_CHECK,使其執(zhí)行non-UFO 的處理邏輯,觸發(fā)_ip_append_data()函數(shù)中的越界寫漏洞。攻擊者可以利用這2 個內(nèi)核漏洞實(shí)現(xiàn)間接容器逃逸,其攻擊流程如下。
1) 構(gòu)造堆布局觸發(fā)CVE-2017-18344,實(shí)現(xiàn)任意地址讀,泄露中斷描述符表(IDT,interrupt descriptor table)第一項(xiàng)(divide_error)的地址,并根據(jù)它的地址計(jì)算出內(nèi)核的加載基址,繞過內(nèi)核KASLR 保護(hù)機(jī)制。
2) 利用CVE-2017-1000112 實(shí)現(xiàn)任意地址寫,劫持控制流至攻擊載荷函數(shù)get_root_payload()。
3) 在 get_root_payload()函數(shù)中首先執(zhí)行commit_creds()函數(shù)來提升容器內(nèi)當(dāng)前進(jìn)程的權(quán)限;然后,執(zhí)行_copy_fs_struct()函數(shù)來將當(dāng)前進(jìn)程的根目錄切換為宿主機(jī)上1 號進(jìn)程的根目錄。
4) 在宿主機(jī)目錄/var/spool/cron/crontabs/下創(chuàng)建文件root,向其中寫入開啟反彈shell 的代碼:echo′ * * * * * bash -c " bash -i >&/dev/tcp/IP/PORT 0>&1 " ′ >> root。
5) 攻擊者在受控端執(zhí)行nc-vnlp 12345 等待容器所在宿主機(jī)反彈shell 的連接,獲取一個root 權(quán)限的反彈shell,實(shí)現(xiàn)逃逸。
由表1 可知,HOC-Detector 可以檢測出該逃逸行為,而NS-Detector 則不可以。HOC-Detector 對利用漏洞CVE-2017-18344 和CVE-2017-1000112實(shí)現(xiàn)的容器逃逸過程進(jìn)行監(jiān)測所獲取的觀測鏈如圖9 所示,在階段2 中,漏洞利用程序exploit 首先執(zhí)行 touch 命令,在宿主機(jī)目錄/var/spool/cron/crontabs 下新建文件root,隨后執(zhí)行write 系統(tǒng)調(diào)用向其寫入內(nèi)容。宿主機(jī)上的進(jìn)程(pid=1121)讀取root 文件中的內(nèi)容,隨后通過執(zhí)行connect 系統(tǒng)調(diào)用與遠(yuǎn)端(攻擊者控制)建立socket 連接,開啟一個root 權(quán)限的反彈shell。漏洞CVE-2017-18344 和CVE-2017-10000112 權(quán)限提升操作處的觀測點(diǎn)屬性變化如表3 所示,容器內(nèi)進(jìn)程(主體T,pid=2874)復(fù)制出子進(jìn)程(客體H,pid=2798),子進(jìn)程的fs_root、uid、gid、cap_permitted 均變?yōu)樗拗鳈C(jī)1 號進(jìn)程對應(yīng)的值()=1,0 ≤i≤ 3),即FAC=4,說明該操作存在權(quán)限提升。同時,容器內(nèi)的進(jìn)程(pid=2798)所執(zhí)行的5.create 和6.write 均是對宿主機(jī)上的文件進(jìn)行操作,也存在權(quán)限提升,HOC-Detector 可以檢測到該間接逃逸攻擊。
表3 漏洞CVE-2017-18344 和CVE-2017-10000112 權(quán)限提升操作處的觀測點(diǎn)屬性變化
圖9 HOC-Detector 對利用漏洞CVE-2017-18344 和CVE-2017-1000112 實(shí)現(xiàn)的容器逃逸過程進(jìn)行監(jiān)測所獲取的觀測鏈
NS-Detecoor 認(rèn)為逃逸進(jìn)程屬于容器進(jìn)程的子進(jìn)程,且其Namespaces 與宿主機(jī)1 號進(jìn)程的namespaces 一致。在本次實(shí)驗(yàn)中,雖然容器內(nèi)進(jìn)程的namespcae 在圖9 中的操作(3.clone)前后發(fā)生變化,如表3 所示,子進(jìn)程(pid=2798)與父進(jìn)程(pid=2784)的net_ns 不一致,但子進(jìn)程(pid=2798)的namespaces 與宿主機(jī)1 號進(jìn)程不一致。即在本次間接逃逸攻擊中,容器內(nèi)新創(chuàng)建進(jìn)程的namespaces與宿主機(jī)1 號進(jìn)程的namespaces 不一致,NS-Detector不能檢測出該間接逃逸行為。此外,圖9 中階段3得到的逃逸進(jìn)程(pid=2845)是宿主機(jī)上cron 進(jìn)程的子進(jìn)程,而非容器進(jìn)程的子進(jìn)程。
CVE-2016-5195(臟牛漏洞)是一個寫時復(fù)制的條件競爭漏洞,影響Linux 內(nèi)核4.8.3 之前的版本。條件競爭[25]是指一個系統(tǒng)的運(yùn)行結(jié)果依賴于不受控制的事件的先后順序,通常發(fā)生在多個進(jìn)程(線程)同時訪問和操作相同的數(shù)據(jù)時。寫時復(fù)制[26]允許不同進(jìn)程中的虛擬內(nèi)存映射到相同物理內(nèi)存頁面的技術(shù)。寫時復(fù)制一般包含3 個重要步驟:創(chuàng)建映射內(nèi)存的副本;更新頁表,使虛擬內(nèi)存指向新創(chuàng)建的物理內(nèi)存;寫入內(nèi)存。由于這3個步驟不是原子性的,一個進(jìn)程在執(zhí)行這3 個步驟過程中可能被其他進(jìn)程中斷,從而觸發(fā)寫時復(fù)制的條件競爭。
攻擊者可利用臟牛漏洞實(shí)現(xiàn)對vDSO[16]的任意寫,從而劫持控制流實(shí)現(xiàn)容器逃逸。vDSO 是內(nèi)核提供的虛擬動態(tài)鏈接庫(.so),當(dāng)程序啟動時,內(nèi)核把vDSO映射入進(jìn)程內(nèi)存空間,程序?qū)⑵洚?dāng)作普通動態(tài)庫來調(diào)用其中的函數(shù)。在Docker 容器中,本文通過利用寫時復(fù)制的條件競爭漏洞(臟牛漏洞)實(shí)現(xiàn)對vDSO 的任意寫,將vDSO 中的函數(shù)clock_gettime()用shellcode覆蓋,進(jìn)而實(shí)現(xiàn)容器逃逸,具體流程如下。
1) 創(chuàng)建一個具有capability SYS_PTRACE 的容器。這是因?yàn)槁┒蠢么a使用PTRACE 進(jìn)行代碼注入,但是Docker 在容器啟動時默認(rèn)過濾了SYS_PTRACE。
2) 運(yùn)行漏洞利用代碼,它將創(chuàng)建2 個線程ptrace_thread 和madvise_thread。
3) 線程ptrace_thread 利用ptrace 不斷向vDSO寫入數(shù)據(jù)。
4) 線程madvise_thread 不斷執(zhí)行madvise 系統(tǒng)調(diào)用,將vDSO 地址空間標(biāo)記為MADV_ DONTNEED,內(nèi)核將會釋放vDSO 所在內(nèi)存區(qū)域,進(jìn)程的頁表會重新指向原始的物理內(nèi)存。
5) 由線程ptrace_thread 和madvise_thread 觸發(fā)寫時復(fù)制的條件競爭漏洞,實(shí)現(xiàn)向只讀內(nèi)存區(qū)域?qū)懭霐?shù)據(jù)的功能,即用shellcode 覆蓋vDSO 中的函數(shù)clock_gettime()。
6) shellcode 會檢查是否是root 權(quán)限進(jìn)程在調(diào)用clock_gettime 函數(shù),若是,則開啟一個root 權(quán)限反彈shell;若不是,則執(zhí)行原來的clock_gettime()函數(shù)。
本次實(shí)驗(yàn)結(jié)果和前一個間接逃逸的實(shí)驗(yàn)結(jié)果一致,HOC-Detector 可以檢測出該逃逸行為,而NS-Detector 則不可以。HOC-Detector 監(jiān)控利用該漏洞進(jìn)行逃逸的流程,得到如圖10 所示的觀測鏈。攻擊者執(zhí)行漏洞利用代碼deadbeef,用shellcode 覆蓋 vDSO 中的函數(shù) clock_gettime()后,進(jìn)程containerd-shim 在某時刻執(zhí)行函數(shù)clock_gettime()開啟一個 root 權(quán)限的 shell(pid=1738)。漏洞CVE-2016-5195 權(quán)限提升操作處的觀測點(diǎn)屬性變化如表4 所示,由于該逃逸行為并未執(zhí)行內(nèi)核函數(shù)commit_creds()來進(jìn)行提權(quán),因此 uid、gid 和cap_permitted 的值并未變化。在內(nèi)核中讀取子進(jìn)程(pid=1739)的fs_root 和namespcae 相關(guān)屬性時,其相關(guān)的指針為空,本文將其屬性值標(biāo)記為None。本文認(rèn)為屬性值為None 是僅低于root 權(quán)限的屬性值。容器內(nèi)進(jìn)程(主體T,pid=21729)復(fù)制出子進(jìn)程(客體H,pid=1739),子進(jìn)程的fs_root、mnt_ns、pid_ns、net_ns 均比父進(jìn)程對應(yīng)的值大()=1,i∈ [0,4,5,6]),即FAC=4,說明該操作存在權(quán)限提升,HOC-Detector 可以檢測到該間接逃逸攻擊。另外,containerd-shim 進(jìn)程(pid=1670)的任務(wù)是用來啟動一個容器,但它通過執(zhí)行3.fork 創(chuàng)建了一個root 權(quán)限的bash 進(jìn)程(逃逸進(jìn)程,pid=1738),超出其原有能力范圍,從此角度看,該操作也存在權(quán)限提升。
表4 漏洞CVE-2016-5195 權(quán)限提升操作處的觀測點(diǎn)屬性變化
圖10 HOC-Detector 對利用漏洞CVE-2016-5195 實(shí)現(xiàn)的容器逃逸過程進(jìn)行監(jiān)測所獲取的觀測鏈
在本次實(shí)驗(yàn)中,NS-detector 不能檢測到容器逃逸攻擊的原因與4.3 節(jié)中的實(shí)驗(yàn)一樣。容器內(nèi)新創(chuàng)建子進(jìn)程的namespaces 并不等于宿主機(jī)1 號進(jìn)程的namespaces,且最后獲得的 root 權(quán)限逃逸進(jìn)程(pid=1738)不是容器內(nèi)進(jìn)程的子進(jìn)程。綜合分析4.3 節(jié)和4.4 節(jié)中的2 個實(shí)驗(yàn),NS-Detector 無法檢測到間接逃逸攻擊的根本原因在于它對容器逃逸行為的畫像不夠全面,僅關(guān)注容器內(nèi)的進(jìn)程和它們的namespaces。HOC-Detector 通過收集宿主機(jī)上所有進(jìn)程的行為信息,提取與容器內(nèi)進(jìn)程所有相關(guān)的操作構(gòu)建觀測鏈。同時,本文經(jīng)過大量復(fù)現(xiàn)內(nèi)核逃逸后提煉出一些具代表性的觀測點(diǎn),實(shí)現(xiàn)對容器內(nèi)進(jìn)程全生命周期的異構(gòu)觀測,使其具備檢測間接逃逸攻擊的能力。
4.5.1 實(shí)驗(yàn)設(shè)置
本文的性能評估方式與CLARION[27]類似,使用基于容器的微服務(wù)數(shù)據(jù)集對 SPADE 和HOC-Detector 進(jìn)行性能開銷的評估。本文使用谷歌提供的一個知名的微服務(wù)集Online Boutique[28]作為本文的數(shù)據(jù)集。該數(shù)據(jù)集目前包含11 個用不同編程語言編寫的微服務(wù),這些微服務(wù)通過gRPC 相互通信。
4.5.2 運(yùn)行時開銷
為了評估HOC-Detector 的運(yùn)行開銷,本文獨(dú)立地啟動每個微服務(wù)50 次,并記錄這50 個容器微服務(wù)初始化的累計(jì)時間。首先,本文在沒有啟動Linux Audit 的情況下執(zhí)行這一流程,以獲得一個基礎(chǔ)測試時間。然后,本文分別用Linux Audit、SPADE和HOC-Detector 重復(fù)這一流程。運(yùn)行時開銷比較如表5 所示。其中,增量開銷是通過對比HOC-Detector和SPADE 的開銷計(jì)算得到的,而總體開銷則是HOC-Detector 與基礎(chǔ)測試的性能比較。本文系統(tǒng)HOC-Detector 基于SPADE 實(shí)現(xiàn),帶來的額外增量開銷低于9%,本文認(rèn)為這是可以接受的。
表5 運(yùn)行時開銷比較
HOC-Detector 的總體開銷包括SPADE 原有的開銷和HOC-Detector 通過內(nèi)核模塊提取進(jìn)程生命周期中觀測點(diǎn)的開銷。通過和Base 的開銷數(shù)值進(jìn)行比較,HOC-Detector 引入的總體開銷平均增加37.3%,對容器的單次啟動開銷增加0.75%。經(jīng)過分析可發(fā)現(xiàn),增加的開銷主要來自SPADE,而不是HOC-Detector 通過內(nèi)核模塊獲取進(jìn)程運(yùn)行時信息的開銷。
針對利用內(nèi)核漏洞的容器逃逸攻擊,本文提出了一種容器逃逸檢測技術(shù)HOC-Detector,通過在內(nèi)核中監(jiān)視進(jìn)程的行為和提取關(guān)鍵進(jìn)程屬性信息,增強(qiáng)了系統(tǒng)對攻擊行為的實(shí)時檢測能力,降低了信息捕捉時延。其次,HOC-Detector 基于進(jìn)程行為信息構(gòu)建進(jìn)程起源圖,將進(jìn)程關(guān)鍵的屬性信息作為觀測點(diǎn),對容器進(jìn)程的全生命周期進(jìn)行異構(gòu)觀測。最后,HOC-Detector 通過檢測容器中的進(jìn)程行為是否有權(quán)限提升來判斷當(dāng)前進(jìn)程是否在實(shí)施容器逃逸攻擊。實(shí)驗(yàn)結(jié)果表明,HOC-Detector 能成功檢測到直接逃逸和間接逃逸攻擊,并且能應(yīng)對不同的攻擊方式。同時,經(jīng)過本文的性能開銷測試,HOC-Detector對容器單次啟動增加的開銷約為0.75%,不會對整個系統(tǒng)造成較大影響。目前,HOC-Detector 還存在一些不足之處。首先,HOC-Detector 僅使用Docker容器下的內(nèi)核漏洞逃逸,尚未對其他容器引擎進(jìn)行測試。其次,針對提取進(jìn)程行為、屬性信息的內(nèi)核模塊,本文尚未涉及對其安全性的討論。這些都將作為筆者未來的工作進(jìn)一步深入研究。