丁 攀,徐雷,劉安,蘇俐竹(中國聯(lián)通研究院,北京 100048)
近10 年來,云計(jì)算的發(fā)展取得了顯著的成就,虛擬化作為其中關(guān)鍵技術(shù)發(fā)展日益迅速,特別是輕量級(jí)的虛擬化技術(shù)——Docker容器技術(shù)。Docker提供一種可移植、可重用且自動(dòng)化的方式來打包和運(yùn)行應(yīng)用。隨著容器技術(shù)的廣泛應(yīng)用,Docker 容器的安全問題日益突出,尤其是容器鏡像安全問題。
鏡像倉庫是容器鏡像的主要傳播方式,Docker Hub 作為Docker 官方的鏡像倉庫,對(duì)于鏡像上傳過程缺乏完善的監(jiān)督,導(dǎo)致鏡像的版本、質(zhì)量和安全性處于不可控的狀態(tài)。文獻(xiàn)[1]分析Docker Hub 倉庫中的2 500個(gè)鏡像后指出,高達(dá)82%被認(rèn)證的鏡像包含至少1個(gè)高危漏洞,而不含漏洞的鏡像僅占17.8%??梢婄R像安全已經(jīng)成為容器面臨的最主要風(fēng)險(xiǎn)之一。為方便讀者更好理解鏡像的脆弱性,本文詳細(xì)分析了鏡像在倉庫和本地存儲(chǔ)的結(jié)構(gòu)特點(diǎn),并針對(duì)性地分析了鏡像存在的安全風(fēng)險(xiǎn)。最后以1個(gè)含有木馬病毒的鏡像為例,通過對(duì)比鏡像文件的變化,分析了非法篡改鏡像的過程,最后簡要介紹了鏡像漏洞掃描器的工作原理。
Docker 鏡像是容器運(yùn)行的模板,它含有啟動(dòng)Docker 容器所需要的文件系統(tǒng)及其內(nèi)容,Docker 鏡像文件與Docker 容器的配置文件共同構(gòu)成了Docker 容器的靜態(tài)文件系統(tǒng)運(yùn)行環(huán)境RootFS,鏡像是容器的靜態(tài)視角,而容器則是鏡像的運(yùn)行狀態(tài)。容器鏡像主要具有以下特點(diǎn)。
a)分層存儲(chǔ):Docker 鏡像采用分層存儲(chǔ)的方式,每個(gè)鏡像都由一系列的層文件(Layer)組成,這些文件按照一定順序排列,不同鏡像層可以共享底層鏡像層文件,從而達(dá)到節(jié)省存儲(chǔ)空間的目的。
b)寫時(shí)復(fù)制:在未更改鏡像文件時(shí),所有的容器共享同樣的鏡像文件,當(dāng)需要修改容器內(nèi)容時(shí),只需修改最上層的鏡像文件。修改后的文件存儲(chǔ)在容器的讀寫層,寫時(shí)復(fù)制與分層存儲(chǔ)一樣,節(jié)省了存儲(chǔ)空間。
c)內(nèi)容尋址:內(nèi)容尋址是指系統(tǒng)根據(jù)鏡像內(nèi)容的Hash值來索引鏡像位置,在對(duì)鏡像執(zhí)行pull、push、load和save等操作時(shí),可以通過Hash值驗(yàn)證鏡像數(shù)據(jù)的完整性,內(nèi)容尋址既提高了鏡像的安全性,又降低了鏡像名稱沖突的可能。
d)聯(lián)合掛載:Docker 鏡像通過聯(lián)合掛載技術(shù)實(shí)現(xiàn)多層文件的疊加,如AUFS、OverlayFS 等。OverlayFS將Linux 主機(jī)上的2 個(gè)目錄合并成1 個(gè)目錄,對(duì)外提供一個(gè)統(tǒng)一的視角,如圖1 所示。下層目錄為只讀的鏡像層。上層目錄為可寫的容器層。合并對(duì)外展示的統(tǒng)一視圖稱為merged 層,在合并后的視圖中,上層目錄會(huì)覆蓋下層目錄的內(nèi)容。當(dāng)需要修改一個(gè)文件時(shí),通過寫時(shí)復(fù)制技術(shù)將文件從只讀的lowerdir 復(fù)制到可寫的upperdir,進(jìn)行修改后保存在upperdir層。
圖1 聯(lián)合文件系統(tǒng)OverlayFS架構(gòu)圖
為了便于理解,本文以Registry 鏡像倉庫為例,來詳細(xì)介紹鏡像在倉庫中的存儲(chǔ)架構(gòu),倉庫以容器的形式啟動(dòng),鏡像在容器中默認(rèn)的存儲(chǔ)位置是/var/lib/registry,通過-v 命令將該目錄掛載到本地/var/lib/registry,該目錄包括blobs和repositories 2個(gè)目錄,其中blobs目錄存儲(chǔ)鏡像文件,repositories 目錄存儲(chǔ)鏡像元數(shù)據(jù),鏡像元數(shù)據(jù)與鏡像文件被設(shè)計(jì)成獨(dú)立隔離存儲(chǔ)。以u(píng)buntu:16.04鏡像為例,當(dāng)registry中存儲(chǔ)該鏡像后,目錄blobs和repositories存儲(chǔ)的具體內(nèi)容如圖2所示。
manifest 文件是存儲(chǔ)在registry 中作為Docker 鏡像的元數(shù)據(jù)文件,在鏡像pull、push、save和load中作為鏡像結(jié)構(gòu)和基礎(chǔ)信息描述文件,當(dāng)鏡像被pull 到宿主機(jī)時(shí),manifest被轉(zhuǎn)化為本地鏡像的配置文件。在存儲(chǔ)鏡像元數(shù)據(jù)文件的目錄repositories中,_layers目錄下link文本文件指向blobs 目錄下與之對(duì)應(yīng)的data 文件,而_manifests 目錄包含鏡像revisions 和tags 信息,其中的current/link文件鏈接到鏡像的manifest文件。
blobs 目錄存儲(chǔ)鏡像文件,它是以data 為名的壓縮文件,目錄名稱為data 的sha256 數(shù)值,即在上文中所述的內(nèi)容尋址,結(jié)果為64 位16 進(jìn)制字符串。為了便于展示,此處縮減為12 位字符串,即58690f9b18fc、b51569e7c507、da8ef40b9eca 和fb15d46c38dc,也被稱為LayerID。通過解壓縮可以看到data 存儲(chǔ)的詳細(xì)內(nèi)容。Docker Daemon 在拉取鏡像時(shí),會(huì)下載data文件并將解壓縮內(nèi)容保存在本地。
目錄b6f507652425 存儲(chǔ)的是鏡像配置文件信息,包括操作系統(tǒng)、鏡像層Diff-ID、鏡像創(chuàng)建歷史、環(huán)境變量、執(zhí)行命令等信息。目錄a3785f78ab85 存儲(chǔ)的是鏡像的manifest,正如文件_manifests/tags/16.04/current/link 所鏈接的,manifest 記錄了鏡像所包含的layer 信息,其中主要信息及其含義分別如下。
mediaType:表示鏡像層文件的類型及其壓縮格式。
size:表示該壓縮包文件的大小。
digest:表示該壓縮包文件的Hash值。
通過讀取manifest 信息,可以獲取鏡像層及各層所占空間等信息,這些信息和圖2 所反饋的信息是一致的。目錄b6f5076524258 存儲(chǔ)的是鏡像配置相關(guān)信息,包括鏡像架構(gòu)、創(chuàng)建過程、容器配置等內(nèi)容,與docker inspect獲取的信息基本一致。除了上述信息以外,還可以驗(yàn)證同一鏡像在不同鏡像倉庫中的manifest存儲(chǔ)路徑和內(nèi)容、blobs目錄下的存儲(chǔ)路徑和內(nèi)容全都是一致的。
圖2 鏡像ubuntu:16.04在倉庫中存儲(chǔ)
本地鏡像在分層存儲(chǔ)過程中,鏡像倉庫中存儲(chǔ)的鏡像可能包含漏洞或木馬病毒文件,當(dāng)被終端下載且未被檢查時(shí),就有可能導(dǎo)致危險(xiǎn)容器鏡像的泛濫。除此之外,容器鏡像也可能包含數(shù)據(jù)庫密碼、證書密鑰以及敏感環(huán)境變量等,存在信息泄露的風(fēng)險(xiǎn)。
docker pull 命令可以下載鏡像,通過下載的提示信息,可以看到鏡像ubuntu:16.04 所對(duì)應(yīng)鏡像層文件(58690f9b18fc、b51569e7c507、da8ef40b9eca、fb15d46c 38dc)都被下載保存到本地,這些層文件ID 即上節(jié)所述的LayerID。從鏡像倉庫中下載鏡像層文件的過程如圖3所示。
圖3 Docker Daemon從Registry下載鏡像的過程
鏡像下載的第1步是獲取鏡像的manifest文件,成功獲取manifest 之后,客戶端通過digest 來獨(dú)立下載鏡像的層文件。如步驟5 所示,此處請(qǐng)求下載鏡像的格式為tar.gz,而Docker Daemon 在實(shí)際的操作過程中,會(huì)將下載容器鏡像進(jìn)行解壓縮并保存在本地,而原壓縮文件并不會(huì)保存下來。鏡像的上傳過程與下載過程相反,客戶端首先需要上傳鏡像各層文件,當(dāng)層文件上傳成功后,還需要更新manifest 的簽名信息,詳細(xì)過程不再贅述。
在容器傳輸過程中,可能存在的安全風(fēng)險(xiǎn)包括鏡像傳輸安全性問題(倉庫與客戶端之間是否通過安全加密傳輸協(xié)議進(jìn)行傳輸)和鏡像傳輸完整性問題(鏡像傳輸過程可能存在中間人攻擊篡改鏡像或者下載文件不完整)。
以overlay2 存儲(chǔ)驅(qū)動(dòng)為例,容器鏡像在本地存儲(chǔ)也是將鏡像元數(shù)據(jù)與鏡像文件完全隔離開,這個(gè)理念與鏡像在倉庫中存儲(chǔ)的設(shè)計(jì)是一致的。鏡像元數(shù)據(jù)存儲(chǔ)路徑是/var/lib/docker/image/overlay2,鏡像文件存儲(chǔ)路徑是/var/lib/docker/overlay2。
鏡像文件在本地的元數(shù)據(jù)包括repository、image、layer 和distribution。由于鏡像是以層的形式來存儲(chǔ)的,所以repository 與image 這2 類元數(shù)據(jù)沒有物理上的鏡像文件與之對(duì)應(yīng),而layer 和distribution 這2 類元數(shù)據(jù)是有物理上的鏡像層文件與之對(duì)應(yīng)的。鏡像在本地的實(shí)際存儲(chǔ)位置是通過CacheID 索引得到的,如圖4所示。
圖4 鏡像文件在本地存儲(chǔ)
repository 元數(shù)據(jù)中存放的是鏡像倉庫相關(guān)的信息,包括repository 的名字、鏡像名稱、鏡像版本和鏡像ID(ImageID)等信息,詳見repositories.json。
imagedb 目錄下保存鏡像的元數(shù)據(jù)配置文件,b6f507652425文件中保存的內(nèi)容與存儲(chǔ)在鏡像倉庫中blobs下的相同文件名下保存的內(nèi)容是一致的。
distribution目錄下保存著LayerID 和DiffID 之間映射關(guān)系,LayerID 為鏡像層壓縮數(shù)據(jù)的SHA256,即鏡像在倉庫中所存儲(chǔ)的DiffID 為下載到本地壓縮狀態(tài)下的鏡像層的SHA256,可以通過以下步驟來驗(yàn)證DiffID 的計(jì)算過程。
a)將鏡像下載到本地。
#docker save ubuntu:16.04>ubuntu.tar
b)解壓縮ubuntu.tar。
#tar-xvf ubuntu.tar
c)計(jì)算不同鏡像層的tar 格式文件的SHA256 數(shù)值,結(jié)果即為DiffID。
#sha256sum layer.tar
layerdb 目錄存儲(chǔ)鏡像的只讀層和讀寫層文件,其標(biāo)識(shí)為ChainID,其值根據(jù)當(dāng)前層和所有祖先層的DiffID算得,計(jì)算公式如下:
目錄layerdb/sha256/
容器在本地存儲(chǔ)過程中,除了存在安全漏洞、鏡像木馬病毒、信息泄露等風(fēng)險(xiǎn)外,還可能存在未經(jīng)授權(quán)非法篡改的問題,啟動(dòng)容器時(shí),系統(tǒng)不會(huì)對(duì)解壓縮后的鏡像文件進(jìn)行二次檢測,此時(shí)鏡像文件可能被非法篡改。
本文使用Dockerscan 工具來模擬非法篡改本地鏡像攻擊,攻擊者將木馬文件植入本地鏡像,當(dāng)被篡改的鏡像運(yùn)行時(shí),攻擊者就會(huì)接收到反彈出的shell,從而達(dá)到控制服務(wù)器的目的。詳細(xì)過程如下。
a)首先導(dǎo)出鏡像文件。
#docker save ubuntu:16.04-o ubuntu-bak
b)向?qū)С龅溺R像文件,添加反彈shell。
#dockerscan image modify trojanize-l 192.168.1.142-p 8888 ubuntu-bak-o ubuntu-attack
c)將原來的鏡像文件替換為添加了反彈shell 的鏡像。
#docker load-i ubuntu-attack.tar
d)在被監(jiān)控端運(yùn)行植入反彈shell的鏡像,在監(jiān)聽端(192.168.1.142)進(jìn)行監(jiān)聽。
完成上述操作后就可以在監(jiān)控端(192.168.1.142)成功監(jiān)控到受控主機(jī)的反彈信息。通過對(duì)比本地存儲(chǔ)鏡像的層文件,可以看出植入反彈shell 的鏡像,在最上層的鏡像文件中多了一個(gè)usr目錄,并且在該目錄中存儲(chǔ)名為reverse_shell.so 的木馬文件。此外,鏡像環(huán)境變量發(fā)生的變化包括:LD_PRELOAD=/usr/share/lib/reverse_shell.so、REMOTE_ADDR=192.168.1.142、REMOTE_PORT=8888。由此可以判斷,該鏡像被非法植入了木馬病毒。
圖5展示了Clair(一種Docker鏡像漏洞掃描工具)的核心部分——ClairCore的功能架構(gòu)。
圖5 ClairCore的功能架構(gòu)圖
ClairCore 有2個(gè)核心組件LibIndex 和LibVuln,當(dāng)鏡像的manifest傳遞到LibIndex 時(shí),LibIndex 會(huì)根據(jù)manifest 將編制鏡像組成內(nèi)容的索引,并創(chuàng)建索引報(bào)告。當(dāng)索引報(bào)告?zhèn)鬟f到LibVuln 時(shí),LibVuln 會(huì)檢索鏡像所存在的漏洞并生成脆弱性報(bào)告。為了使讀者更好地理解LibIndex 和LibVuln 的工作原理,圖6 描述了LibInder中核心的索引器(Indexer)的工作流程,圖7描述了LibVuln中的匹配器(Matcher)的數(shù)據(jù)流程圖。
圖6 Indexer工作流程圖
圖7 Matcher數(shù)據(jù)流程圖
在圖7中,當(dāng)IndexReport作為參數(shù)調(diào)用LibVuln的Scan 方法時(shí),將開啟容器鏡像漏洞的匹配過程。圖7展示的是一個(gè)可能出現(xiàn)的數(shù)據(jù)流程圖,根據(jù)提供的IndexReport,數(shù)據(jù)將被解包到不同的流程中,匹配器將評(píng)估流中的每個(gè)數(shù)據(jù),并確定是否存在漏洞,最終將匹配結(jié)果合并到脆弱性報(bào)告中,并將其反饋給客戶端。
為了使讀者更好地理解容器鏡像安全掃描的過程,本文詳細(xì)介紹了容器鏡像在本地節(jié)點(diǎn)及鏡像倉庫的存儲(chǔ)原理。無論是在本地存儲(chǔ)還是在倉庫中存儲(chǔ),鏡像元數(shù)據(jù)與鏡像文件是完全隔離存儲(chǔ)的,這是鏡像存儲(chǔ)設(shè)計(jì)的一個(gè)基本思想。在不同鏡像倉庫中,鏡像manifest 存儲(chǔ)路徑和內(nèi)容、blobs 目錄下的存儲(chǔ)路徑和內(nèi)容完全一致。相同鏡像在不同主機(jī)上的LayerID、DiffID、ChainID也是完全一致的,通過元數(shù)據(jù)信息獲得CacheID,進(jìn)而通過CacheID 索引到鏡像在本地的存儲(chǔ)路徑。
為了保證容器鏡像的安全,容器鏡像在存儲(chǔ)過程中應(yīng)具備保證數(shù)據(jù)完整性、機(jī)密性、可用性的能力,所以容器倉庫應(yīng)具備審核和加密存儲(chǔ)鏡像的能力。容器鏡像掃描是發(fā)現(xiàn)容器安全漏洞、惡意代碼的重要手段,但是鏡像掃描不能解決容器生命周期中存在的所有問題,例如容器基礎(chǔ)環(huán)境、容器編排器配置、共享資源、未知漏洞等安全問題。所以,只有將鏡像安全掃描與容器運(yùn)行時(shí)監(jiān)控、容器安全編排等工具結(jié)合起來,才能實(shí)現(xiàn)容器全生命周期的安全管控。