張云濤,方濱興,2,杜春來,王忠儒,崔志堅,宋首友,5
(1.北京郵電大學(xué)網(wǎng)絡(luò)空間安全學(xué)院,北京 100876;2.廣州大學(xué)網(wǎng)絡(luò)空間先進技術(shù)研究院,廣東 廣州 510006;3.北方工業(yè)大學(xué)信息學(xué)院,北京 100144;4.中國網(wǎng)絡(luò)空間研究院信息化研究所,北京 100010;5.北京丁牛科技有限公司,北京 100081)
在數(shù)字經(jīng)濟背景下,云計算被寫入《中華人民共和國國民經(jīng)濟和社會發(fā)展第十四個五年規(guī)劃和2035 年遠景目標綱要》。中國信息通信研究院數(shù)據(jù)顯示,預(yù)計到2024 年,我國云市場規(guī)模將接近7 800 億元。以容器為代表的云原生技術(shù)蓬勃發(fā)展,為云計算數(shù)字化賦能提供了重要推動力,并為軟件開發(fā)和系統(tǒng)運維帶來了顛覆性的變革。容器技術(shù)[1]是一種輕量級的虛擬化技術(shù),可以實現(xiàn)應(yīng)用程序在虛擬化隔離環(huán)境中的穩(wěn)定運行。在傳統(tǒng)操作系統(tǒng)級別的虛擬化技術(shù)中,每個實例都需要運行客戶端操作系統(tǒng)的完整副本,因此造成了大量的硬件模擬開銷[2]。容器是進程級的虛擬化,不需要模擬硬件,相比于傳統(tǒng)虛擬化技術(shù)少了很多開銷,從而可以輕量高效地運行應(yīng)用程序。容器編排通過微服務(wù)體系結(jié)構(gòu)模式來實現(xiàn)業(yè)務(wù)流程自動化,是大規(guī)模容器應(yīng)用的重要工具。容器編排工具(如 Kubernetets(K8S)[3]和 Docker Swarm[4])的出現(xiàn)極大地方便了容器化應(yīng)用程序的部署、擴展和管理。目前,容器技術(shù)已經(jīng)廣泛應(yīng)用于大型互聯(lián)網(wǎng)公司的生產(chǎn)環(huán)境和云服務(wù)供應(yīng)商,如Amazon Fargate、Microsoft Azure Kubernetes 等。
容器技術(shù)雖然已經(jīng)在實際生產(chǎn)環(huán)境被廣泛使用,但存在較多潛在的安全威脅,如容器逃逸、資源隔離失效、鏡像安全威脅、橫向移動攻擊、拒絕服務(wù)攻擊、運行環(huán)境未加固等[5]。其中,容器逃逸直接影響了承載容器的底層基礎(chǔ)設(shè)施的安全性和可用性,進而對生產(chǎn)環(huán)境造成了安全隱患。具體來說,容器逃逸[6]是指攻擊者利用程序、系統(tǒng)的漏洞或缺陷,突破容器與宿主機之間的隔離機制,獲得在宿主機上的命令執(zhí)行能力。目前,導(dǎo)致容器逃逸的主要因素包括配置不當(dāng)、應(yīng)用程序漏洞和系統(tǒng)內(nèi)核漏洞。相比于前兩者,系統(tǒng)內(nèi)核漏洞導(dǎo)致的逃逸危害更大、威脅更廣。這是因為容器與宿主機共用一個內(nèi)核,攻擊者可以利用內(nèi)核漏洞進行攻擊,從而突破隔離“逃逸”至宿主機,對宿主機和其他容器造成了極大的安全威脅。因此,如何檢測利用內(nèi)核漏洞的容器逃逸具有重要的研究意義。由于Docker 是當(dāng)前使用范圍最廣的容器引擎之一,本文專注于檢測Linux 平臺下利用內(nèi)核漏洞實現(xiàn)的Docker 容器逃逸。
目前,Linux 平臺下對利用內(nèi)核漏洞實現(xiàn)容器逃逸的檢測方法主要是基于運行時的異常檢測[7-8]。Salamero[7]使用eBPF 實現(xiàn)了一個進程異常行為檢測工具Falco,可通過連續(xù)監(jiān)視Linux 內(nèi)核中的系統(tǒng)調(diào)用來捕捉異常行為。但是Falco 主要基于預(yù)先設(shè)定的規(guī)則來判斷異常行為,不能有效防御未知漏洞的攻擊。Jian 等[8]提出基于容器內(nèi)進程所屬命名空間(namespaces)[9]的變化來檢測容器逃逸行為。但該方案存在2 個缺陷:檢測滯后性和檢測失效性。
為了解決現(xiàn)有容器逃逸檢測中漏報率較高的問題,本文提出了一種異構(gòu)觀測的實時檢測方法,并實現(xiàn)了基于異構(gòu)觀測鏈的容器逃逸檢測原型系統(tǒng),簡稱HOC-Detector。首先,通過大量復(fù)現(xiàn)利用內(nèi)核漏洞實現(xiàn)的容器逃逸攻擊案例,對容器逃逸流程進行建模,將相應(yīng)的攻擊案例劃分為直接逃逸和間接逃逸。容器逃逸后獲得root 權(quán)限的逃逸進程是容器內(nèi)進程的子進程則為直接逃逸;反之則為間接逃逸。然后,對已有利用內(nèi)核漏洞的容器逃逸行為進行觀測,并總結(jié)、提煉出一系列觀測點,包括進程的用戶ID(uid)、組ID(gid)、能力(capabilities)[10]、根目錄(root directory)以及namespaces 等。同時提出容器逃逸是由一系列以“權(quán)限提升”為目的的攻擊手段組合而成的。最后,基于捕獲的進程行為生成容器進程的起源圖,結(jié)合觀測點構(gòu)建容器進程逃逸行為的異構(gòu)觀測鏈,以是否出現(xiàn)“權(quán)限提升”為檢測標準,對容器進程全生命周期進行觀測,實現(xiàn)對利用內(nèi)核漏洞實施容器逃逸的檢測。本文的主要貢獻總結(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)核漏洞的容器逃逸流程進行建模,提煉出容器逃逸行為的主要觀測點和基于“權(quán)限提升”的容器逃逸檢測標準。
2) 利用Linux 內(nèi)核模塊實現(xiàn)了對容器進程操作行為的捕獲和進程屬性信息的提取,進一步生成了進程起源圖。
3) 提出異構(gòu)觀測的檢測方法,基于容器進程起源圖和觀測點構(gòu)建異構(gòu)觀測鏈,從多個角度對容器進程所執(zhí)行的操作進行全生命周期的異構(gòu)觀測,檢測是否有權(quán)限提升的情況。
4) 為了驗證系統(tǒng)的有效性,選取4 個具有代表性的容器逃逸內(nèi)核漏洞,并與容器逃逸檢測工具NS-Detector 進行對比。其中,利用這4 個漏洞實現(xiàn)的容器逃逸涵蓋了本文總結(jié)出的兩類逃逸方式:直接逃逸和間接逃逸。實驗結(jié)果表明,本文所提方法可以實時檢測直接和間接逃逸。同時,基于真實場景的實驗分析表明,本文的系統(tǒng)有較小的性能開銷。
Linux 下的容器技術(shù)基于內(nèi)核中的namespaces[9]實現(xiàn)隔離控制,基于cgroups[11]實現(xiàn)資源分配。由于容器進程與宿主機進程共享系統(tǒng)內(nèi)核,攻擊者可以在容器內(nèi)利用內(nèi)核漏洞實現(xiàn)容器逃逸,進而對整個物理機環(huán)境造成危害[6]。為了在逃逸后獲得root 權(quán)限的交互式命令解釋器(shell),攻擊者通常會先提升當(dāng)前進程的權(quán)限,因此可以通過動態(tài)實時監(jiān)控容器內(nèi)進程是否有提權(quán)行為來防御逃逸。Lin 等[12]提出的利用內(nèi)核漏洞的提權(quán)一般包含4 個步驟:繞過內(nèi)核地址空間布局隨機化(KASLR,kernel address space layout randomization)[13]、繞過管理模式訪問保護(SMAP,supervisor mode access prevention)和管理模式執(zhí)行保護(SMEP,supervisor mode execution prevention)[14]、覆蓋內(nèi)核函數(shù)指針劫持控制流和調(diào)用內(nèi)核函數(shù)commit_creds()。Lin 等認為前3 個步驟很難直接檢測,所以提出通過檢測進程是否調(diào)用函數(shù)commit_creds()來阻止容器內(nèi)進程的提權(quán)行為。但是,在實踐中存在一些其他的內(nèi)核提權(quán)方法,并不會通過調(diào)用函數(shù)comm_cred()來進行提權(quán)。比如,攻擊者可以利用Linux 內(nèi)核寫時復(fù)制(copy-on-write)中的條件競爭漏洞[15],繞過虛擬動態(tài)共享對象(vDSO,virtual dynamic shared object)[16]對進程內(nèi)存權(quán)限的限制,將漏洞利用代碼(shellcode)[17]注入vDSO 實現(xiàn)提權(quán)并逃逸。
此外,Jian 等[8]提出了一種基于進程所屬namespaces狀態(tài)變化的方案NS-Detecto(rnamespaces detector)來檢測Docker 容器的逃逸行為。Jian 等認為,當(dāng)攻擊者從容器中逃逸至宿主機并獲得一個可控的shell 時,該進程仍屬于容器中進程的子進程,但其namespaces 已突破容器與宿主機間的隔離,屬于宿主機進程的namespaces。該方案主要存在2 個缺陷:一是檢測滯后性,因為容器逃逸通常在最后一步才會突破namespaces 的隔離;二是檢測失效性,攻擊者在容器內(nèi)利用內(nèi)核漏洞提權(quán)并獲得對宿主機目錄操作權(quán)限后,可以通過服務(wù)cron 創(chuàng)建定時任務(wù)的方式來獲取一個反彈shell[18],實現(xiàn)“間接”容器逃逸。此時,獲得的可控shell 與容器中進程無直接關(guān)系,該方案不能檢測出此類型的容器逃逸。
另一類常見的檢測容器逃逸的方法是安全容器[19-20]。與普通容器相比,安全容器運行在一個獨立的微型虛擬機中,擁有完整的操作系統(tǒng)內(nèi)核。安全容器是通過隔離層增強容器的安全性,即在容器和內(nèi)核之間增加隔離層來阻止逃逸。典型的安全容器技術(shù)有Kata[19]和gVisor[20]。安全容器雖然實現(xiàn)了容器和內(nèi)核的安全隔離,但中間隔離層的引入增加了攻擊面,可能會導(dǎo)致其他未知的風(fēng)險。
首先,本文討論的容器逃逸檢測技術(shù)主要針對普通容器。其次,Lin 等的工作用于檢測內(nèi)核提權(quán),Jian 等提出的NS-Detector 是目前唯一用于檢測利用內(nèi)核漏洞實現(xiàn)容器逃逸的工作,與本文研究內(nèi)容直接相關(guān)。總體來說,當(dāng)前對利用內(nèi)核漏洞實現(xiàn)容器逃逸行為的檢測方案主要面臨檢測漏報率較高的問題。在2.3 節(jié)中,本文對利用內(nèi)核實現(xiàn)的容器逃逸建模后將逃逸行為劃分為直接逃逸和間接逃逸。當(dāng)前的容器逃逸檢測方案僅能檢測直接逃逸,無法有效檢測間接逃逸。本文提出異構(gòu)觀測鏈的方法可以在保證檢測直接逃逸的前提下檢測間接逃逸。
2008 年,Moreau 等[21]提出了開放起源模型(OPM,open provenance model)。OPM 的本質(zhì)是一個包含不同對象間依賴關(guān)系的有向無環(huán)圖,可用于描述某一對象在特定時間階段的所有操作。OPM 示例如圖1 所示,共定義了3 類節(jié)點。

圖1 OPM 示例
1) 狀態(tài)(artifact):用橢圓形表示,代表不可變的狀態(tài),對應(yīng)于計算機中的數(shù)據(jù)對象。
2) 過程(process):用矩形表示,代表施加在數(shù)據(jù)對象上的行為,可以產(chǎn)生新的狀態(tài)節(jié)點。
3) 代理(agent):用六邊形表示,用于控制或影響過程的執(zhí)行。
3 類節(jié)點之間的邊代表不同的因果(依賴)關(guān)系:有向邊的頭部是結(jié)果,尾部(箭頭)是原因。如圖1 所示,3 類節(jié)點之間有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是父進程,P2是子進程。
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),也有可能被多個代理控制,所以需要準確地識別這些狀態(tài)集和代理集。OPM 引入角色的概念,如圖1 所示,每條邊上標注了角色(R),并且給每個狀態(tài)和代理規(guī)定唯一標識的角色。
在Linux 系統(tǒng)中,根據(jù)特權(quán)級別不同,將進程運行狀態(tài)劃分為用戶態(tài)和內(nèi)核態(tài)。應(yīng)用程序一般運行在用戶態(tài),當(dāng)其執(zhí)行系統(tǒng)調(diào)用或發(fā)生中斷而陷入內(nèi)核中執(zhí)行時,就稱進程處于內(nèi)核態(tài)。當(dāng)進程在內(nèi)核態(tài)時,可以直接訪問操作系統(tǒng)內(nèi)核中與進程屬性相關(guān)的數(shù)據(jù)結(jié)構(gòu)。因此,攻擊者可以利用內(nèi)核漏洞劫持控制流來執(zhí)行惡意代碼修改容器進程與權(quán)限相關(guān)的數(shù)據(jù)結(jié)構(gòu),從而提升容器進程的權(quán)限,進一步實現(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)核中與進程管理和控制相關(guān)最重要的數(shù)據(jù)結(jié)構(gòu)。該結(jié)構(gòu)體伴隨進程整個生命周期,記錄進程的當(dāng)前狀態(tài)以及控制進程運行的全部信息(打開的文件、信號量、進程狀態(tài)、地址空間等)。
2) cred:系統(tǒng)通過該結(jié)構(gòu)體控制進程對其他資源的操作權(quán)限判定。當(dāng)進程操作消息隊列、共享內(nèi)存和信息量等數(shù)據(jù)對象時,系統(tǒng)需要對進程的euid、egid 進行檢查;當(dāng)進程執(zhí)行文件相關(guān)的操作時,系統(tǒng)需要對進程的fsuid、fsgid 進行檢查。同時,Linux使用能力(capability)機制,以細粒度方式控制普通進程能否執(zhí)行“特權(quán)”操作。例如,進程要掛載(mount)一個文件系統(tǒng),那么進程需要有對應(yīng)的capability,即CAP_SYS_ADMIN。
3) fs_struct:用于描述進程的文件系統(tǒng)信息。結(jié)構(gòu)體中的root 和pwd 分別代表進程的根目錄和當(dāng)前工作目錄。
4) nsproxy:用于描述進程各個namespaces 的狀態(tài)。其中,pid namespaces 為進程提供了一個具有獨立進程ID 的運行環(huán)境。在每一個pid namespaces 中,進程的pid 從1 開始編號,且和其他pid namespaces 中的pid 互不影響。
利用內(nèi)核漏洞實現(xiàn)容器逃逸一般可分為兩步,攻擊者首先利用內(nèi)核漏洞劫持內(nèi)核的控制流,然后執(zhí)行惡意代碼實現(xiàn)容器逃逸。在復(fù)現(xiàn)并觀測大量利用內(nèi)核漏洞的容器逃逸后,本文將逃逸行為建模為如圖3 所示的流程。根據(jù)逃逸后獲取的root 權(quán)限進程(shell)是否為容器中進程的子進程,將容器逃逸分為兩類:直接逃逸和間接逃逸。

圖3 利用內(nèi)核漏洞的直接、間接逃逸流程
2.3.1 直接逃逸
在直接逃逸中,攻擊者在劫持內(nèi)核控制流之后一般會提升當(dāng)前容器進程的權(quán)限,確保逃逸后可以獲得root 權(quá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)前進程(cur)的cred 替換為root 權(quán)限的cred。然后,執(zhí)行內(nèi)核函數(shù)switch_task_namespacess(cur,INIT_NSPROXY) 突破系統(tǒng)對容器內(nèi)進程的Namespaces 隔離。最后,cur 進程創(chuàng)建一個root 權(quán)限的逃逸進程,實現(xiàn)直接容器逃逸。
2.3.2 間接逃逸
間接逃逸與直接逃逸最大的區(qū)別是不需要容器內(nèi)的進程突破namespaces 的隔離也能獲得root權(quá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)前進程的權(quán)限和將進程根目錄切換為宿主機上1 號進程的根目錄。此時,攻擊者可以通過改寫宿主機文件的方式實現(xiàn)逃逸。例如,容器中的進程在宿主機/var/spool/cron/crontabs/目錄下新建root 文件,向其中寫入執(zhí)行反彈shell 的命令,當(dāng)宿主機的root 權(quán)限的進程cron 執(zhí)行該文件中的命令后獲得一個root權(quán)限的交互式shell。最后得到的root 權(quán)限進程與容器中的進程并無直接繼承關(guān)系,實現(xiàn)間接容器逃逸。另一種實現(xiàn)間接逃逸的方式是在劫持內(nèi)核控制流后不調(diào)用內(nèi)核函數(shù),而是用shellcode 覆蓋內(nèi)核中的函數(shù)。如圖3 中的3.2) 所示,攻擊者覆蓋內(nèi)核中的clock_gettime()函數(shù),隨后宿主機上root 權(quán)限的進程調(diào)用clock_gettime()函數(shù)獲取時間時會開啟一個root 權(quán)限的shell,實現(xiàn)間接逃逸。
因此,利用內(nèi)核漏洞實現(xiàn)的容器逃逸在其逃逸過程中所執(zhí)行的操作一般會表現(xiàn)出“權(quán)限提升”的特點。從攻防雙方來看,攻擊者可以憑此探索新的容器逃逸方法,一旦有新的內(nèi)核漏洞,就可以考慮是否可用于容器逃逸;防守者則可以針對此特征來檢測容器進程生命周期中有無權(quán)限提升發(fā)生,以此來防御利用內(nèi)核漏洞實現(xiàn)的容器逃逸。
進程執(zhí)行不同操作前后的屬性變化能體現(xiàn)出權(quán)限提升,而進程屬性本質(zhì)上由內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)來控制。因此,為了觀測進程生命周期中是否存在權(quán)限提升,本文基于內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)選取觀測點列表S為[fs_root,cap_permited,uid,gid,mnt_ns,pid_ns,net_ns],其中,fs_root 表示進程的根目錄,cap_permited 表示進程所能夠擁有特權(quán)的上限,uid和gid 分別表示進程所屬的用戶ID 和組ID,觀測點列表中剩余部分均與進程的namespaces 相關(guān)。
為檢測上述2 類容器逃逸,本文基于進程的操作構(gòu)建進程起源圖,以此來描述進程行為之間的關(guān)系。隨后,結(jié)合進程起源圖和觀測點構(gòu)建異構(gòu)觀測鏈,對進程完整生命周期進行監(jiān)控,即檢測進程行為引發(fā)的觀測點屬性變化情況,若存在權(quán)限提升的屬性變化,則判定進程正在進行逃逸。
本文所提出的基于異構(gòu)觀測鏈的容器進程實時檢測系統(tǒng)旨在通過對容器內(nèi)的進程進行全生命周期的監(jiān)控來檢測是否存在利用內(nèi)核漏洞的容器逃逸行為。為此,一方面需要確保監(jiān)控和檢測過程的實時性,在不能引入過高的性能開銷的同時也需要保證系統(tǒng)的穩(wěn)定運行;另一方面需要保證檢測的有效性。因此,系統(tǒng)的設(shè)計目標包括以下4 個方面。
1) 保證捕捉進程行為和提取進程屬性信息的實時性。基于此要求,HOC-Detector 需要及時捕獲進程的操作,然后提取其行為、屬性信息。
2) 盡量減小逃逸檢測時延。為減小檢測逃逸的時延,除需要快速提取進程的行為、屬性信息外,還需要縮小逃逸檢測的范圍。
3) 引入較小的性能開銷。在確保進程信息提取的實時性、檢測低時延的前提下,盡量減小HOC-Detector 對系統(tǒng)造成運行時開銷。
4) 能夠防御未知威脅。對于使用未知內(nèi)核漏洞進行容器逃逸的行為,HOC-Detector 要能準確地捕捉,以確保逃逸檢測的有效性。
進程的動態(tài)行為可由其所執(zhí)行的一系列操作來刻畫。當(dāng)進程進行容器逃逸時,往往會執(zhí)行一些特別的操作來繞過系統(tǒng)中的安全機制,而操作前后進程的屬性變化可以反映出這些操作是否合法。為此,本文借助OPM 將系統(tǒng)中進程執(zhí)行的所有操作抽象為進程起源圖,從起源圖上提取容器內(nèi)進程的觀測鏈。如圖4 所示,該觀測鏈由進程、文件等對象以及表征其相關(guān)操作的有向依賴邊構(gòu)成。

圖4 基于進程行為起源圖的異構(gòu)觀測鏈
本文將觀測鏈中依賴邊的尾部(箭頭)節(jié)點稱為主體,用T(tail)表示;邊的頭部節(jié)點稱為客體,用H(head)表示;主體和客體均可以是進程、文件等。進程用Ppid表示,其中pid 為進程號;文件等其他資源用Aname表示,其中name 為標識符。主體與客體間的行為用Oop表示,其中op∈{fork,clone,read,write,…};主體對客體之間的操作關(guān)系用TOopH 表示。
如圖4 所示,節(jié)點代表containerd-shim 進程,在T2時刻,容器中的init 進程 P1021復(fù)制了一個子進程 P1022,該操作可以表示為 P1021OforkP1022。在T3時刻,P1022運行漏洞利用程序后創(chuàng)建了一個新的進程P1023,該操作可以表示為 P1022OforkP1023。在T4時刻,該進程讀取了宿主機中/etc/shadow 文件中的內(nèi)容P1023OreadAshadow。從圖 4 中得到的觀測鏈為(P1021OforkP1022) ∧(P1022OforkP1023) ∧(P1023OreadAshadow)。其中,符號∧用來連接觀測鏈上進程的操作。
在得到容器內(nèi)進程的觀測鏈后,本文基于2.3 節(jié)中提出的觀測點列表S,在進程生命周期中不同的時間節(jié)點對進程的操作進行異構(gòu)觀測。采用3.5 節(jié)中提出的檢測標準來評估進程操作是否存在權(quán)限提升,以此來判斷是否發(fā)生了逃逸行為。使用異構(gòu)觀測鏈的方法來檢測容器逃逸有2 個明顯的優(yōu)勢,一是異構(gòu)觀測鏈涵蓋進程在不同時間點所執(zhí)行的操作,可以在早期就檢測到進程的逃逸行為,顯著降低逃逸檢測的時延;二是異構(gòu)觀測鏈包含進程生命周期中所有相關(guān)的操作,可以對進程實現(xiàn)全面的監(jiān)控,顯著降低逃逸檢測的漏報。
HOC-Detector 總體架構(gòu)如圖5 所示,由進程日志獲取模塊(AuditLogger)、進程起源圖生成模塊(PPGGenerator )、異構(gòu)觀測鏈構(gòu)建模塊(HOCBuilder)、攻擊檢測模塊(AttackDetector)和預(yù)先配置的觀測點(ObservationPoints)五部分組成。在HOC-Detector 運行過程中,AuditLogger 在系統(tǒng)內(nèi)核中監(jiān)視進程,并將其行為、屬性信息保存在宿主機的日志文件中;與此同時,PPGGenerator 從日志文件中讀取內(nèi)容,采取基于因果關(guān)系的方法構(gòu)建進程的起源圖,并將其傳遞給 HOCBuilder;HOCBuilder 基于起源圖并結(jié)合用戶提供的觀測點構(gòu)建異構(gòu)觀測鏈,AttackDetector 對異構(gòu)觀測鏈進行分析,檢測容器進程是否存在逃逸行為。
為了對基于內(nèi)核的容器逃逸有更清晰的了解,本文在2.3 節(jié)中通過對內(nèi)核漏洞導(dǎo)致的容器逃逸進行大量復(fù)現(xiàn)后,對其逃逸流程進行建模。同時,從是否能很好地表征權(quán)限提升的角度選取進程的屬性信息作為觀測點。如圖5 中的ObservationPoints所示,觀測點列表為[S[1],S[2],…,S[m]]。同時,為了增加對未知威脅的防御能力,可以動態(tài)調(diào)整HOC-Detector 中的觀測點。

圖5 HOC-Dectector 總體架構(gòu)
在HOC-Detector 的運行過程中,AuditLogger需要持續(xù)實時地捕捉進程的行為和記錄屬性的信息。因此,本文使用內(nèi)核編程的方式通過動態(tài)可加載的內(nèi)核模塊在內(nèi)核中截取進程所執(zhí)行的系統(tǒng)調(diào)用,記錄其系統(tǒng)調(diào)用名、參數(shù)和返回值等信息,實現(xiàn)對進程行為的實時監(jiān)控。HOC-Detector 的設(shè)計目標是檢測利用內(nèi)核漏洞的容器逃逸,而容器本質(zhì)上是宿主機上使用namespaces 和cgroups 實現(xiàn)資源隔離的普通進程。宿主機上的進程數(shù)目龐大,為了實現(xiàn)容器逃逸檢測的實時性,本文需要重點關(guān)注容器相關(guān)的進程。因此,需要識別出容器內(nèi)外進程的邊界,減輕后續(xù)異構(gòu)觀測鏈構(gòu)建和攻擊檢測的復(fù)雜度。PPGGenerator 對記錄的進程日志信息進行語義解析,構(gòu)建進程起源圖。隨后,HOCBuilder 結(jié)合起源圖和觀測點構(gòu)建異構(gòu)觀測鏈。最后,AttackDetector 基于異構(gòu)觀測鏈檢測進程的整個生命周期中是否存在權(quán)限提升的情況,判斷是否存在容器逃逸行為。如圖5 中的AttackDetector 所示,該模塊對進程操作前后的每個觀測點Si進行對比,判斷其是否存在權(quán)限提升,ai表示檢測結(jié)果。當(dāng)超過半數(shù)的觀測點存在權(quán)限提升時,本文判定當(dāng)前進程存在逃逸行為。
Linux 系統(tǒng)中的/proc 目錄是一種虛擬文件系統(tǒng),該目錄下的文件描述了內(nèi)核當(dāng)前的運行狀態(tài),可以在用戶態(tài)通過讀取/proc/pid 目錄下文件的內(nèi)容來查看當(dāng)前正在運行進程的信息。該方法主要面臨2 個挑戰(zhàn):一是系統(tǒng)中的進程數(shù)目龐大,頻繁讀取/proc 目錄下文件中的內(nèi)容開銷很大;二是攻擊者在實施攻擊的過程中可能會隱藏一些進程,導(dǎo)致不能在/proc 目錄下找到對應(yīng)進程的信息。因此,本文通過內(nèi)核模塊編程的方式在內(nèi)核態(tài)提取與進程相關(guān)的關(guān)鍵屬性信息,并將其保存到宿主機的日志文件中。
本文所選取的觀測點可以通過進程描述符task_struct 獲取,所以首先需要得到進程的task_struct結(jié)構(gòu)體,然后根據(jù)其結(jié)構(gòu)體成員提取進程觀測點信息。該功能由HOC-Detector 中的AuditLogger 模塊實現(xiàn),采用Linux Audit[22]技術(shù)在內(nèi)核中捕捉系統(tǒng)中所有進程的各種操作行為,如系統(tǒng)調(diào)用、文件讀寫、執(zhí)行命令等。其具體實現(xiàn)流程如下。
1) 在內(nèi)核態(tài)截取與新進程創(chuàng)建相關(guān)的系統(tǒng)調(diào)用,例如clone、fork 和vfork。當(dāng)進程執(zhí)行這些系統(tǒng)調(diào)用后,將其返回值傳給/include/linux/pid.h 中的函數(shù)find_vpid()以獲取新進程的struct pid 結(jié)構(gòu)體指針,它指向的結(jié)構(gòu)體保存了新進程的進程描述符信息。
2) 把步驟1)中指向struct pid 的指針作為函數(shù)pid_task()的參數(shù),得到進程的task_struct 結(jié)構(gòu)體。
3) 利用__task_cred()讀取task_struct 中的指針cred 所指向的結(jié)構(gòu)體,從中提取進程的用戶和組權(quán)限uid、gid、euid、fsuid 等。同時,提取進程的細粒度權(quán)限信息cap_permitted。
4) 通過task_struct中的指針fs指向的結(jié)構(gòu)體獲得進程根目錄root,然后根據(jù)root 中的dentry 得到根目錄項中表示所有子目錄的鏈表指針d_subdirs。最后,通過遍歷d_subdirs 得到根目錄下所有子目錄或子文件的名字。
5) 通過task_struct 中的指針nsproxy 指向的結(jié)構(gòu)體獲得進程namespaces 的信息。
6) 系統(tǒng)中的每個進程可能屬于多個不同的namespaces,通過結(jié)構(gòu)體struct pid 中的numbers 數(shù)組來獲取進程在不同pid namespaces 中的進程編號。在容器逃逸場景下,pid 表示進程在宿主機環(huán)境下的進程號(全局進程號),vpid 表示容器中進程在其隔離的pid namespaces 中的進程號(局部進程號)。
如圖6 所示,runc 進程(父進程,pid=2556)正在執(zhí)行容器初始化,它通過執(zhí)行系統(tǒng)調(diào)用號為56的clone 來創(chuàng)建一個子進程(pid=2557)。本文將進程編號2557 作為參數(shù)依次執(zhí)行上面描述的操作來提取新進程的信息。為了方便對比在新進程創(chuàng)建時有無權(quán)限提升的情況發(fā)生,本文也會提取父進程的相關(guān)屬性信息。如圖6 所示,子進程的命名空間與父進程的命名空間(mnt_ns,pid_ns,net_ns)不一致,并且子進程的局部進程編號為1(vpid=1),說明該子進程是新啟動的容器中的init 進程。為了方便表示進程的根目錄,本文將容器內(nèi)的進程根目錄root_path 標識為container,容器外進程標識為host。

圖6 內(nèi)核模塊提取的進程觀測點信息
宿主機操作系統(tǒng)使用Linux 中的Namespaces技術(shù)來限制容器進程可見的資源,使運行在同一宿主機下不同容器中的程序之間互不影響。使用cgroups 技術(shù)來限制進程可使用的資源,確保宿主機上所有容器公平使用宿主機上的資源,如CPU、內(nèi)存、磁盤和網(wǎng)絡(luò)等。所以,容器僅是宿主機上的一個特殊的進程,與其他進程相比沒有本質(zhì)的區(qū)別。但Linux Audit 在記錄進程信息時并不會區(qū)分當(dāng)前進程是普通進程還是容器進程,而容器內(nèi)進程的行為是本文檢測容器逃逸的關(guān)鍵。為了優(yōu)化異構(gòu)觀測鏈的構(gòu)建以及提高容器逃逸的檢測效率,本文提出建立對容器內(nèi)外進程邊界的識別,該過程僅需關(guān)注與容器內(nèi)進程及其行為相關(guān)的進程。
通過分析Docker 容器的初始化流程可以發(fā)現(xiàn),容器的啟動有固定的模式,基于此來識別容器內(nèi)外進程的邊界。容器的初始化以執(zhí)行系統(tǒng)調(diào)用unshare創(chuàng)建新的命名空間系統(tǒng)開始,以執(zhí)行系統(tǒng)調(diào)用execve 在容器內(nèi)創(chuàng)建新的init 進程結(jié)束。本文以Docker 容器的初始化為例,如圖7 所示,其初始化流程介紹如下。

圖7 Docker 容器初始化流程
1) dockerd 通過gPRC 接口向容器運行時管理引擎containerd 發(fā)送指令創(chuàng)建容器,隨后containerd(pid=1405)會啟動一個進程 containerd-shim(pid=2522),由它負責(zé)創(chuàng)建一個新的容器。
2) 該進程(pid=2522)會復(fù)制出一系列的containerd-shim 進程,其中一個 containerd-shim(pid=2532)會創(chuàng)建新的runC 進程來完成容器初始化操作。
3) 進程 runc(pid=2540)會復(fù)制出子進程runc:[0:PARENT],該進程的子進程runc:[1:CHILD]會執(zhí)行系統(tǒng)調(diào)用unshare 創(chuàng)建新的命名空間,標志著開始進行實際的容器初始化。
4) 進程 runc:[1:CHILD]會復(fù)制一些子進程runc:[2:INIT]來完成一些特定的初始化任務(wù),包括設(shè)置/proc 和/rootfs 等。
5) 最后,進程runc:[1:CHILD]將復(fù)制一個子進程(pid=2563),該子進程即容器內(nèi)的init 進程,它將執(zhí)行系統(tǒng)調(diào)用execve 來運行容器默認的啟動程序(如bash)。
HOC-Dectector 中的PPGGenerator 模塊基于系統(tǒng)收集的進程行為信息構(gòu)建進程起源圖,本文使用上述容器初始化模式在起源圖上標記容器內(nèi)外進程的邊界,僅關(guān)注容器內(nèi)的進程,減輕后續(xù)構(gòu)建觀測鏈的工作量。
觀測鏈的構(gòu)建依賴于能夠?qū)⑦M程生命周期內(nèi)所有行為之間的依賴關(guān)系抽象為有向的進程起源圖。HOC-Detector 中的HOCBuilder 模塊利用開源框架SPADE[23]來提取進程起源圖,利用3.4 節(jié)中描述的方法識別容器內(nèi)外進程的邊界和負責(zé)啟動容器的containerd-shim 進程。然后,以啟動容器的containerd-shim 進程為根節(jié)點,以廣度優(yōu)先的方式遍歷起源圖,直到覆蓋所有子節(jié)點或者達到最大深度,最終從完整的起源圖中提取出與容器進程相關(guān)的子圖。如圖4 所示,本文將從containerd-shim 進程的節(jié)點到葉節(jié)點的一個節(jié)點序列稱為觀測鏈。
如2.3 節(jié)所述,本文選取的觀測點列表為S。本文在AttackDetector 中將主體T 和客體H 之間單個觀測點的檢測標準Δ定義為

其中,m為本文選取的觀測點列表S的長度,如2.3 節(jié)所述,m=7。冗余技術(shù)在軟件系統(tǒng)中可以極大提升系統(tǒng)的容錯能力,從而提高系統(tǒng)的穩(wěn)定性。本文使用具有冗余能力的多數(shù)表決算法[24]來判定當(dāng)前操作是否存在權(quán)限提升。其基本思想是如果有超過半數(shù)觀測點的檢測標準Δ值為1,則判定為當(dāng)前主客體間的操作存在權(quán)限提升的行為,即如果(當(dāng)m=7時,F(xiàn)AC≥ 4)則判定當(dāng)前的主客體間的操作存在權(quán)限提升,否則為正常操作。進程的觀測鏈由多條邊組成,只要有一條邊(主客體間的操作)存在權(quán)限提升,本文就認為該進程發(fā)生容器逃逸。
為了驗證HOC-Dectector 在防御利用內(nèi)核漏洞的容器逃逸攻擊時的有效性,本文選取了4 個影響較大的、可通過不同方式實現(xiàn)容器逃逸的CVE 內(nèi)核漏洞。本文的實驗環(huán)境主要由物理機和容器引擎Docker 組成,物理機的配置為Intel(R) Core(TM)i7-9750H,4 核處理器,10 GB 內(nèi)存,200 GB 硬盤,具體的CVE 信息和系統(tǒng)實驗環(huán)境如表1 所示。本文選擇的CVE包括CVE-2017-7308、CVE-2017-18344、CVE-2017-1000112 和CVE-2016-5195。第一個CVE 可通過調(diào)用內(nèi)核函數(shù)的方式實現(xiàn)直接容器逃逸;第二個和第三個CVE 一起使用,可通過調(diào)用內(nèi)核函數(shù)實現(xiàn)間接容器逃逸;最后一個CVE 可通過不調(diào)用內(nèi)核函數(shù)的方式實現(xiàn)間接容器逃逸。
為檢驗HOC-Detector 的有效性,本文復(fù)現(xiàn)了Jian 等提出的方法 NS-Detector 并與之對比。NS-Detector 通過監(jiān)測進程所屬namespaces 的狀態(tài)變化來檢測針對Docker 容器的逃逸攻擊。其主要思想是攻擊者實現(xiàn)容器逃逸攻擊后獲得的root 權(quán)限逃逸進程仍屬于容器內(nèi)進程的子進程,但其所屬Namespaces 已脫離容器的限制。本文將NS-Detector和HOC-Detector 在前述3 個容器逃逸的CVE 實驗中進行對比,實驗結(jié)果如表 1 所示。其中,HOC-Detector 能檢測出所有的容器逃逸,準確率達到100%,而NS-Detector 的準確率約為33%。

表1 容器逃逸檢測的內(nèi)核漏洞信息及實驗環(huán)境和結(jié)果
AF_PACKET 是在Linux 平臺下的一種套接字(socket),用于在設(shè)備驅(qū)動層發(fā)送或者接收數(shù)據(jù)包。進程可以使用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ù)包會存放在一個單獨的幀(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)核漏洞進一步實現(xiàn)容器逃逸的攻擊流程如下。
1) 通過/proc/kallsyms 或syslog 的方式泄露內(nèi)核函數(shù)地址來獲得內(nèi)核的加載基址,繞過內(nèi)核KASLR 保護機制。
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ù)。若計時器超時,執(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)前進程的權(quán)限;然后,執(zhí)行switch_task_namespaces()來切換容器內(nèi)已提權(quán)進程的namespaces,實現(xiàn)逃逸。由表1 可知,HOC-Detector 和NS-Detector 均能檢測到利用漏洞CVE-2017-7308 實現(xiàn)的逃逸行為。HOC-Detector 對利用漏洞CVE-2017-7308實現(xiàn)的容器逃逸過程進行監(jiān)測所獲取的觀測鏈如圖8 所示。攻擊者通過運行逃逸攻擊程序exploit 獲得一個root 權(quán)限的進程(pid=3046)。其中,進程(pid=3045)執(zhí)行系統(tǒng)調(diào)用clone 產(chǎn)生新進程(pid=3046)的操作存在權(quán)限提升。漏洞CVE-2017-7308 權(quán)限提升操作處的觀測點屬性變化如表2 所示。其中,容器內(nèi)的進程(主體T,pid=3045)復(fù)制出的子進程(客體H,pid=3046)在所有觀測點的值均與宿主機上1 號進程的值相等()=1,0 ≤i≤ 6),即FAC=7,說明出現(xiàn)了權(quán)限提升,HOC-Detector 可以檢測出利用該內(nèi)核漏洞實現(xiàn)的直接逃逸行為。由于獲得的逃逸進程(pid=3046)是容器中進程(pid=3045)的子進程,且兩者的namespaces 不一致,NS-Detector 也能檢測到該逃逸行為。

表2 漏洞CVE-2017-7308 權(quán)限提升操作處的觀測點屬性變化

圖8 HOC-Detector 對利用漏洞CVE-2017-7308 實現(xiàn)的容器逃逸過程進行監(jiān)測所獲取的觀測鏈
CVE-2017-18344 存在于Linux 內(nèi)核4.14.8 之前的版本中,在系統(tǒng)調(diào)用 timer_create 的實現(xiàn)kernel/time/posix-timers.c 中沒有正確驗證結(jié)構(gòu)體sigevent 中的字段sigev_notify,存在整型溢出漏洞。當(dāng)用戶讀取/proc/pid/timers 中的內(nèi)容時,可以利用該漏洞在posix-timers.c 的函數(shù)show_timer()中實現(xià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)核通過標志SO_NO_CHECK 來判斷在處理數(shù)據(jù)包時是使用UFO 機制還是non-UFO機制(UFO 機制是指用網(wǎng)卡輔助進行報文分片,用戶層協(xié)議不進行分片;non-UFO 機制是指在用戶層進行報文分片)。具體來說,第一次調(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)核漏洞實現(xiàn)間接容器逃逸,其攻擊流程如下。
1) 構(gòu)造堆布局觸發(fā)CVE-2017-18344,實現(xiàn)任意地址讀,泄露中斷描述符表(IDT,interrupt descriptor table)第一項(divide_error)的地址,并根據(jù)它的地址計算出內(nèi)核的加載基址,繞過內(nèi)核KASLR 保護機制。
2) 利用CVE-2017-1000112 實現(xiàn)任意地址寫,劫持控制流至攻擊載荷函數(shù)get_root_payload()。
3) 在 get_root_payload()函數(shù)中首先執(zhí)行commit_creds()函數(shù)來提升容器內(nèi)當(dāng)前進程的權(quán)限;然后,執(zhí)行_copy_fs_struct()函數(shù)來將當(dāng)前進程的根目錄切換為宿主機上1 號進程的根目錄。
4) 在宿主機目錄/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 等待容器所在宿主機反彈shell 的連接,獲取一個root 權(quán)限的反彈shell,實現(xiàn)逃逸。
由表1 可知,HOC-Detector 可以檢測出該逃逸行為,而NS-Detector 則不可以。HOC-Detector 對利用漏洞CVE-2017-18344 和CVE-2017-1000112實現(xiàn)的容器逃逸過程進行監(jiān)測所獲取的觀測鏈如圖9 所示,在階段2 中,漏洞利用程序exploit 首先執(zhí)行 touch 命令,在宿主機目錄/var/spool/cron/crontabs 下新建文件root,隨后執(zhí)行write 系統(tǒng)調(diào)用向其寫入內(nèi)容。宿主機上的進程(pid=1121)讀取root 文件中的內(nèi)容,隨后通過執(zhí)行connect 系統(tǒng)調(diào)用與遠端(攻擊者控制)建立socket 連接,開啟一個root 權(quán)限的反彈shell。漏洞CVE-2017-18344 和CVE-2017-10000112 權(quán)限提升操作處的觀測點屬性變化如表3 所示,容器內(nèi)進程(主體T,pid=2874)復(fù)制出子進程(客體H,pid=2798),子進程的fs_root、uid、gid、cap_permitted 均變?yōu)樗拗鳈C1 號進程對應(yīng)的值()=1,0 ≤i≤ 3),即FAC=4,說明該操作存在權(quán)限提升。同時,容器內(nèi)的進程(pid=2798)所執(zhí)行的5.create 和6.write 均是對宿主機上的文件進行操作,也存在權(quán)限提升,HOC-Detector 可以檢測到該間接逃逸攻擊。

表3 漏洞CVE-2017-18344 和CVE-2017-10000112 權(quán)限提升操作處的觀測點屬性變化

圖9 HOC-Detector 對利用漏洞CVE-2017-18344 和CVE-2017-1000112 實現(xiàn)的容器逃逸過程進行監(jiān)測所獲取的觀測鏈
NS-Detecoor 認為逃逸進程屬于容器進程的子進程,且其Namespaces 與宿主機1 號進程的namespaces 一致。在本次實驗中,雖然容器內(nèi)進程的namespcae 在圖9 中的操作(3.clone)前后發(fā)生變化,如表3 所示,子進程(pid=2798)與父進程(pid=2784)的net_ns 不一致,但子進程(pid=2798)的namespaces 與宿主機1 號進程不一致。即在本次間接逃逸攻擊中,容器內(nèi)新創(chuàng)建進程的namespaces與宿主機1 號進程的namespaces 不一致,NS-Detector不能檢測出該間接逃逸行為。此外,圖9 中階段3得到的逃逸進程(pid=2845)是宿主機上cron 進程的子進程,而非容器進程的子進程。
CVE-2016-5195(臟牛漏洞)是一個寫時復(fù)制的條件競爭漏洞,影響Linux 內(nèi)核4.8.3 之前的版本。條件競爭[25]是指一個系統(tǒng)的運行結(jié)果依賴于不受控制的事件的先后順序,通常發(fā)生在多個進程(線程)同時訪問和操作相同的數(shù)據(jù)時。寫時復(fù)制[26]允許不同進程中的虛擬內(nèi)存映射到相同物理內(nèi)存頁面的技術(shù)。寫時復(fù)制一般包含3 個重要步驟:創(chuàng)建映射內(nèi)存的副本;更新頁表,使虛擬內(nèi)存指向新創(chuàng)建的物理內(nèi)存;寫入內(nèi)存。由于這3個步驟不是原子性的,一個進程在執(zhí)行這3 個步驟過程中可能被其他進程中斷,從而觸發(fā)寫時復(fù)制的條件競爭。
攻擊者可利用臟牛漏洞實現(xiàn)對vDSO[16]的任意寫,從而劫持控制流實現(xiàn)容器逃逸。vDSO 是內(nèi)核提供的虛擬動態(tài)鏈接庫(.so),當(dāng)程序啟動時,內(nèi)核把vDSO映射入進程內(nèi)存空間,程序?qū)⑵洚?dāng)作普通動態(tài)庫來調(diào)用其中的函數(shù)。在Docker 容器中,本文通過利用寫時復(fù)制的條件競爭漏洞(臟牛漏洞)實現(xiàn)對vDSO 的任意寫,將vDSO 中的函數(shù)clock_gettime()用shellcode覆蓋,進而實現(xiàn)容器逃逸,具體流程如下。
1) 創(chuàng)建一個具有capability SYS_PTRACE 的容器。這是因為漏洞利用代碼使用PTRACE 進行代碼注入,但是Docker 在容器啟動時默認過濾了SYS_PTRACE。
2) 運行漏洞利用代碼,它將創(chuàng)建2 個線程ptrace_thread 和madvise_thread。
3) 線程ptrace_thread 利用ptrace 不斷向vDSO寫入數(shù)據(jù)。
4) 線程madvise_thread 不斷執(zhí)行madvise 系統(tǒng)調(diào)用,將vDSO 地址空間標記為MADV_ DONTNEED,內(nèi)核將會釋放vDSO 所在內(nèi)存區(qū)域,進程的頁表會重新指向原始的物理內(nèi)存。
5) 由線程ptrace_thread 和madvise_thread 觸發(fā)寫時復(fù)制的條件競爭漏洞,實現(xiàn)向只讀內(nèi)存區(qū)域?qū)懭霐?shù)據(jù)的功能,即用shellcode 覆蓋vDSO 中的函數(shù)clock_gettime()。
6) shellcode 會檢查是否是root 權(quán)限進程在調(diào)用clock_gettime 函數(shù),若是,則開啟一個root 權(quán)限反彈shell;若不是,則執(zhí)行原來的clock_gettime()函數(shù)。
本次實驗結(jié)果和前一個間接逃逸的實驗結(jié)果一致,HOC-Detector 可以檢測出該逃逸行為,而NS-Detector 則不可以。HOC-Detector 監(jiān)控利用該漏洞進行逃逸的流程,得到如圖10 所示的觀測鏈。攻擊者執(zhí)行漏洞利用代碼deadbeef,用shellcode 覆蓋 vDSO 中的函數(shù) clock_gettime()后,進程containerd-shim 在某時刻執(zhí)行函數(shù)clock_gettime()開啟一個 root 權(quán)限的 shell(pid=1738)。漏洞CVE-2016-5195 權(quán)限提升操作處的觀測點屬性變化如表4 所示,由于該逃逸行為并未執(zhí)行內(nèi)核函數(shù)commit_creds()來進行提權(quán),因此 uid、gid 和cap_permitted 的值并未變化。在內(nèi)核中讀取子進程(pid=1739)的fs_root 和namespcae 相關(guān)屬性時,其相關(guān)的指針為空,本文將其屬性值標記為None。本文認為屬性值為None 是僅低于root 權(quán)限的屬性值。容器內(nèi)進程(主體T,pid=21729)復(fù)制出子進程(客體H,pid=1739),子進程的fs_root、mnt_ns、pid_ns、net_ns 均比父進程對應(yīng)的值大()=1,i∈ [0,4,5,6]),即FAC=4,說明該操作存在權(quán)限提升,HOC-Detector 可以檢測到該間接逃逸攻擊。另外,containerd-shim 進程(pid=1670)的任務(wù)是用來啟動一個容器,但它通過執(zhí)行3.fork 創(chuàng)建了一個root 權(quán)限的bash 進程(逃逸進程,pid=1738),超出其原有能力范圍,從此角度看,該操作也存在權(quán)限提升。

表4 漏洞CVE-2016-5195 權(quán)限提升操作處的觀測點屬性變化

圖10 HOC-Detector 對利用漏洞CVE-2016-5195 實現(xiàn)的容器逃逸過程進行監(jiān)測所獲取的觀測鏈
在本次實驗中,NS-detector 不能檢測到容器逃逸攻擊的原因與4.3 節(jié)中的實驗一樣。容器內(nèi)新創(chuàng)建子進程的namespaces 并不等于宿主機1 號進程的namespaces,且最后獲得的 root 權(quán)限逃逸進程(pid=1738)不是容器內(nèi)進程的子進程。綜合分析4.3 節(jié)和4.4 節(jié)中的2 個實驗,NS-Detector 無法檢測到間接逃逸攻擊的根本原因在于它對容器逃逸行為的畫像不夠全面,僅關(guān)注容器內(nèi)的進程和它們的namespaces。HOC-Detector 通過收集宿主機上所有進程的行為信息,提取與容器內(nèi)進程所有相關(guān)的操作構(gòu)建觀測鏈。同時,本文經(jīng)過大量復(fù)現(xiàn)內(nèi)核逃逸后提煉出一些具代表性的觀測點,實現(xiàn)對容器內(nèi)進程全生命周期的異構(gòu)觀測,使其具備檢測間接逃逸攻擊的能力。
4.5.1 實驗設(shè)置
本文的性能評估方式與CLARION[27]類似,使用基于容器的微服務(wù)數(shù)據(jù)集對 SPADE 和HOC-Detector 進行性能開銷的評估。本文使用谷歌提供的一個知名的微服務(wù)集Online Boutique[28]作為本文的數(shù)據(jù)集。該數(shù)據(jù)集目前包含11 個用不同編程語言編寫的微服務(wù),這些微服務(wù)通過gRPC 相互通信。
4.5.2 運行時開銷
為了評估HOC-Detector 的運行開銷,本文獨立地啟動每個微服務(wù)50 次,并記錄這50 個容器微服務(wù)初始化的累計時間。首先,本文在沒有啟動Linux Audit 的情況下執(zhí)行這一流程,以獲得一個基礎(chǔ)測試時間。然后,本文分別用Linux Audit、SPADE和HOC-Detector 重復(fù)這一流程。運行時開銷比較如表5 所示。其中,增量開銷是通過對比HOC-Detector和SPADE 的開銷計算得到的,而總體開銷則是HOC-Detector 與基礎(chǔ)測試的性能比較。本文系統(tǒng)HOC-Detector 基于SPADE 實現(xiàn),帶來的額外增量開銷低于9%,本文認為這是可以接受的。

表5 運行時開銷比較
HOC-Detector 的總體開銷包括SPADE 原有的開銷和HOC-Detector 通過內(nèi)核模塊提取進程生命周期中觀測點的開銷。通過和Base 的開銷數(shù)值進行比較,HOC-Detector 引入的總體開銷平均增加37.3%,對容器的單次啟動開銷增加0.75%。經(jīng)過分析可發(fā)現(xiàn),增加的開銷主要來自SPADE,而不是HOC-Detector 通過內(nèi)核模塊獲取進程運行時信息的開銷。
針對利用內(nèi)核漏洞的容器逃逸攻擊,本文提出了一種容器逃逸檢測技術(shù)HOC-Detector,通過在內(nèi)核中監(jiān)視進程的行為和提取關(guān)鍵進程屬性信息,增強了系統(tǒng)對攻擊行為的實時檢測能力,降低了信息捕捉時延。其次,HOC-Detector 基于進程行為信息構(gòu)建進程起源圖,將進程關(guān)鍵的屬性信息作為觀測點,對容器進程的全生命周期進行異構(gòu)觀測。最后,HOC-Detector 通過檢測容器中的進程行為是否有權(quán)限提升來判斷當(dāng)前進程是否在實施容器逃逸攻擊。實驗結(jié)果表明,HOC-Detector 能成功檢測到直接逃逸和間接逃逸攻擊,并且能應(yīng)對不同的攻擊方式。同時,經(jīng)過本文的性能開銷測試,HOC-Detector對容器單次啟動增加的開銷約為0.75%,不會對整個系統(tǒng)造成較大影響。目前,HOC-Detector 還存在一些不足之處。首先,HOC-Detector 僅使用Docker容器下的內(nèi)核漏洞逃逸,尚未對其他容器引擎進行測試。其次,針對提取進程行為、屬性信息的內(nèi)核模塊,本文尚未涉及對其安全性的討論。這些都將作為筆者未來的工作進一步深入研究。