DevOps概念的流行跟近些年微服務(wù)架構(gòu)的興起有很大關(guān)系,DevOps是Dev(Development)和Ops(Operations)的結(jié)合,Dev負(fù)責(zé)開發(fā),Ops負(fù)責(zé)部署上線,Docker出現(xiàn)之前,公司需要搭建一個(gè)數(shù)據(jù)庫環(huán)境,有了Docker之后,只需在一些開源的基礎(chǔ)鏡像上構(gòu)建出公司自己的鏡像即可。
因此目前大多數(shù)DevOps設(shè)置都在CI管道中的某處設(shè)置了Docker,這就意味著你所看到的任何構(gòu)建環(huán)境都將使用Docker等容器解決方案。由于這些構(gòu)建環(huán)境需要接受不可信的用戶提供的代碼并進(jìn)行執(zhí)行,因此探討如何將這些代碼安全地裝入容器就顯得非常有意義。
在這篇文章中,我將探討在構(gòu)建環(huán)境中非常小的錯(cuò)誤配置是如何產(chǎn)生嚴(yán)重的安全風(fēng)險(xiǎn)的。
需要注意的是,我并未在本文描述Heroku,Docker,AWS CodeBuild或容器中的任何固有漏洞,而是討論了在查看基于Docker容器的多租戶構(gòu)建環(huán)境時(shí)發(fā)現(xiàn)的錯(cuò)誤配置漏洞。在常規(guī)運(yùn)行下,雖然Docker容器技術(shù)提供了非常穩(wěn)定的安全默認(rèn)設(shè)置,但是在特殊情況時(shí),有時(shí)候小的錯(cuò)誤配置就會(huì)導(dǎo)致嚴(yán)重安全風(fēng)險(xiǎn)。
特殊的構(gòu)建環(huán)境
可能的特殊構(gòu)建環(huán)境可以具有以下架構(gòu):
1.具有完全托管的生成服務(wù),可編譯源代碼、運(yùn)行測(cè)試以及生成可供部署的軟件包——AWS CodeBuild;
2.Docker構(gòu)建服務(wù)中的Docker容器;
Docker容器可以通過Dind(Docker-in-Docker,是讓你可以在Docker容器里面運(yùn)行Docker的一種方式)創(chuàng)建,因此,從理論上來說,你最終得到兩個(gè)攻擊者需要逃脫的容器。使用CodeBuild可進(jìn)一步最小化攻擊面,因?yàn)槟銚碛蠥WS提供的一次性容器,而且租戶不會(huì)與對(duì)方的構(gòu)建過程互動(dòng)。
攻擊者是如何控制構(gòu)建過程的?
在大多數(shù)構(gòu)建或CI管道中要做的第一件事就是創(chuàng)建一個(gè)包含你想要構(gòu)建和部署的代碼的Git倉庫。然后這些代碼將被打包并轉(zhuǎn)移到構(gòu)建環(huán)境,最后應(yīng)用到docker構(gòu)建過程。
通過查看構(gòu)建服務(wù),你通??梢酝ㄟ^兩種方式配置容器,即通過Dockerfile或config.yml,這兩種方法都與源代碼有關(guān)。
其中有一個(gè)CI配置文件,我稱之為config-ci.yml,如下圖所示。
在其它的構(gòu)建過程開始之前,該文件將在構(gòu)建過程中被轉(zhuǎn)換為Dockerfile。如果你有明確指定要使用的Dockerfile環(huán)境,可以將config-ci.yml更改為以下內(nèi)容。
Dockerfile_Web和Dockerfile_Worker是源代碼存儲(chǔ)庫中Dockerfiles的相對(duì)路徑和名稱,既然現(xiàn)在我已經(jīng)提供了完整的構(gòu)建信息,就可以開始構(gòu)建了。構(gòu)建通常是通過原始倉庫上的代碼上傳來啟動(dòng)的。啟動(dòng)時(shí),你會(huì)看到如下所示的輸出內(nèi)容。
正如你所看到的,輸出的內(nèi)容有docker build -f Dockerfile。這些內(nèi)容即對(duì)調(diào)試過程有用,又對(duì)于發(fā)現(xiàn)可能出現(xiàn)的攻擊有用。
對(duì)預(yù)構(gòu)建過程進(jìn)行攻擊
在進(jìn)入docker構(gòu)建之前,我首先想到的是嘗試并中斷構(gòu)建過程,或者,我可以嘗試將來自CodeBuild環(huán)境的文件鏈接到我的Docker構(gòu)建的上下文中。
由于我已經(jīng)控制了config-ci.yml文件的內(nèi)容,更具體地說,我控制的是“要使用的Dockerfile的相對(duì)路徑”,所以我可以嘗試用一種老式攻擊方法——目錄遍歷攻擊。
第一個(gè)嘗試就是試著改變構(gòu)建的目錄:
一旦構(gòu)建過程開始,我就會(huì)立即得到錯(cuò)誤信息。
有趣的是,該錯(cuò)誤是我造成的,并導(dǎo)致了路徑泄漏,如果我嘗試“讀取”文件會(huì)發(fā)生什么?
可以看出,我解析了Docker守護(hù)進(jìn)程的錯(cuò)誤。不幸的是,這只針對(duì)我系統(tǒng)上的第一行文件。盡管如此,這也是一個(gè)有趣的開始。
其實(shí),我這么做的另一個(gè)想法是想嘗試使用符號(hào)鏈接將文件包含到我的構(gòu)建中。不過,Docker阻止了我這么做,因?yàn)樗粫?huì)將構(gòu)建目錄之外的文件包含到構(gòu)建上下文中。
攻擊構(gòu)建過程,以發(fā)現(xiàn)漏洞
讓我們先回到實(shí)際的構(gòu)建過程,看看可以對(duì)什么進(jìn)行攻擊?由于構(gòu)建過程發(fā)生在dind Docker容器中,該容器在一次性CodeBuild實(shí)例中運(yùn)行。為了進(jìn)一步尋找攻擊,docker構(gòu)建過程會(huì)在一次性Docker容器中運(yùn)行所有命令。Docker的容器是把應(yīng)用程序和環(huán)境打包在一起的,所以是一次構(gòu)建,多處發(fā)布。舉個(gè)例子,以前你開發(fā)完程序后,測(cè)試人員和運(yùn)維人員都需要去部署,通過docker只需要一個(gè)run命令即可。因此docker最大的好處就是標(biāo)準(zhǔn)化了應(yīng)用交互,同時(shí)支持多個(gè)不同平臺(tái)和多個(gè)不同的云服務(wù)商,只要機(jī)器能裝docker,就能無差別的運(yùn)行程序。
所以Docker構(gòu)建的每一步實(shí)際上都是一個(gè)新的Docker容器,這從構(gòu)建過程的輸出中就可以看出。
在上述情況下,在新的Docker容器e7e10023b1fc中執(zhí)行上面輸出代碼段中的Step 2/9。因此,即使用戶決定在Dockerfile中插入一些惡意代碼,它們也應(yīng)該在一次性隔離容器中運(yùn)行,而不會(huì)造成任何損害,如下所示。
在發(fā)布Docker命令時(shí),這些命令實(shí)際上被傳遞給負(fù)責(zé)創(chuàng)建/運(yùn)行/管理Docker鏡像的dockerd守護(hù)進(jìn)程。為了繼續(xù)實(shí)現(xiàn)dind,dind需要運(yùn)行自己的Docker守護(hù)進(jìn)程。然而,由于實(shí)現(xiàn)dind的方式是使用主機(jī)系統(tǒng)的docker實(shí)例(dockerd instance),以允許主機(jī)和后臺(tái)共享Docker鏡像,并從Docker的所有緩存中受益。
如果Dind使用下面的包裝腳本啟動(dòng)會(huì)發(fā)生什么結(jié)果:
/usr/local/bin/dind是一個(gè)使Docker在容器中運(yùn)行的包裝腳本,該包裝腳本確保來自主機(jī)的Docker套接字在容器內(nèi)部可用,因此,此特定配置會(huì)引入安全漏洞。
通常Docker構(gòu)建過程將無法與Docker守護(hù)進(jìn)程交互,但是,在這種情況下,卻可以實(shí)現(xiàn)交互。敏銳的觀察者可能會(huì)注意到,dockerd守護(hù)進(jìn)程的TCP端口也是通過--host=tcp://0.0.0.0:2375進(jìn)行映射的。通過這種錯(cuò)誤配置設(shè)置的Docker守護(hù)進(jìn)程會(huì)監(jiān)控容器上的所有接口。因此,這就成了Docker網(wǎng)絡(luò)功能中的一個(gè)漏洞。除非另有說明,否則所有容器都將會(huì)被放入相同的默認(rèn)Docker網(wǎng)絡(luò)中。這意味著每個(gè)容器都能夠與其他容器進(jìn)行通信,而不會(huì)受到阻礙。
現(xiàn)在,我的臨時(shí)構(gòu)建容器(執(zhí)行用戶代碼的那個(gè)容器)已經(jīng)能夠向托管它的dind容器發(fā)出網(wǎng)絡(luò)請(qǐng)求。由于dind容器只是重復(fù)使用了主機(jī)系統(tǒng)的Docker守護(hù)進(jìn)程,所以我實(shí)際上是直接向主機(jī)系統(tǒng)AWS CodeBuild發(fā)出命令。
實(shí)施Dockerfiles攻擊
為了測(cè)試Dockerfiles攻擊,我可以將下面的Dockerfile提供給構(gòu)建系統(tǒng),這樣我就能夠交互訪問正在構(gòu)建的容器。需要說明的是,我這么做只是為了加速尋找漏洞的過程,而不是為了減少等待構(gòu)建過程的時(shí)間。
可以看出,反向shell可以通過很多不同的方式完成。
這個(gè)Dockerfile會(huì)安裝一些依賴項(xiàng),即docker和netcat。然后它們會(huì)將我的源代碼目錄中的文件復(fù)制到構(gòu)建容器中。這將在后來的步驟中用到,除此之外,這么做還可以更容易地將我的完整漏洞快速傳輸?shù)较到y(tǒng)。由于mknod指令會(huì)創(chuàng)建一個(gè)文件系統(tǒng)節(jié)點(diǎn),以允許通過文件重定向stdin和stdout。使用netcat可以打開一個(gè)反向shell,除此之外,我還需要在我使用公共IP地址控制的系統(tǒng)上為此反向shell設(shè)置監(jiān)控器。
這樣,當(dāng)構(gòu)建發(fā)生時(shí),我將收到一個(gè)反向連接。
現(xiàn)在通過遠(yuǎn)程交互式訪問,我就可以檢查是否能對(duì)Docker守護(hù)進(jìn)程進(jìn)行訪問。
我會(huì)使用-H 172.18.0.1來指定遠(yuǎn)程主機(jī),由于我發(fā)現(xiàn)Docker使用的網(wǎng)絡(luò)范圍是172.18.0.0/16,因此使用了此地址。為了找到這個(gè)遠(yuǎn)程主機(jī),我的交互式shell被用來充作ip addr和ip route,以獲得分配給我的構(gòu)建容器的網(wǎng)絡(luò)。請(qǐng)注意,默認(rèn)情況下,所有Docker容器都將被放入同一個(gè)網(wǎng)絡(luò),默認(rèn)網(wǎng)關(guān)將是運(yùn)行Docker守護(hù)進(jìn)程的實(shí)例。
這樣漏洞就會(huì)被成功發(fā)現(xiàn),此時(shí)我可以從正在構(gòu)建的容器中訪問Docker,以便在下一步啟動(dòng)一個(gè)具有額外特權(quán)的新容器。
進(jìn)行棧處理
此時(shí),我已有一個(gè)shell,不過它還是位于一次性的構(gòu)建容器中,作用不是很大。另外,我也可以訪問Docker守護(hù)進(jìn)程。于是我就想,把這兩者結(jié)合起來會(huì)怎么樣?為此,我引入了第二個(gè)Dockerfile,它會(huì)在構(gòu)建和運(yùn)行時(shí)創(chuàng)建一個(gè)反向shell。以下就是我啟動(dòng)第二個(gè)監(jiān)控器來捕獲的新的shell。
這將作為Dockerfile2保存在源代碼目錄中,現(xiàn)在,當(dāng)源代碼文件被復(fù)制到構(gòu)建容器中時(shí),我可以直接訪問它了。
當(dāng)我重新運(yùn)行構(gòu)建過程時(shí),我將在端口4445上獲得我的第一個(gè)反向shell,這樣我就可以留在構(gòu)建容器中?,F(xiàn)在我可以構(gòu)建Dockerfile2,它被復(fù)制到COPY * /files/中的構(gòu)建容器中。
現(xiàn)在我可以使用主機(jī)Docker守護(hù)進(jìn)程并構(gòu)建一個(gè)新的可用Docker映像,我只需要運(yùn)行它即可。不過這里有個(gè)小的技巧,就是我需要通將根目錄映射到新的Docker容器,這可以通過-v/:/vhost完成。
以下是我得到的第一個(gè)反向shell:
現(xiàn)在,一個(gè)新的反向shell就會(huì)連接到攻擊系統(tǒng)上的4446端口。這樣我就將處于一個(gè)新的容器中,并直接訪問底層CodeBuild主機(jī)的文件系統(tǒng)和網(wǎng)絡(luò)。這首先是因?yàn)?-net=host將通過主機(jī)網(wǎng)絡(luò)映射,而不是將容器保存在一個(gè)獨(dú)立的隔離網(wǎng)絡(luò)中。其次,因?yàn)镈ocker守護(hù)進(jìn)程正在主機(jī)系統(tǒng)上運(yùn)行,所以當(dāng)使用-v /:/vhost的文件映射完成時(shí),主機(jī)系統(tǒng)的文件系統(tǒng)將被映射。
這樣在新的反向shell中,我現(xiàn)在就可以探索底層的主機(jī)文件系統(tǒng)了。通過檢查以下兩個(gè)之間的區(qū)別,我就可以證明我在與此文件系統(tǒng)交互時(shí)不在Docker中。
在/vhost中我還發(fā)現(xiàn)有一個(gè)新的目錄,它可以清楚地表明我在CodeBuild實(shí)例文件系統(tǒng)中,而不是在任何Docker容器中。
這樣在codebuild里,就會(huì)出現(xiàn)一個(gè)神奇的結(jié)果。這是AWS Codebuild用來控制構(gòu)建環(huán)境的內(nèi)容,快速瀏覽一下可以看到一些有趣的數(shù)據(jù)。
此時(shí),我通常會(huì)嘗試提取AWS憑證和數(shù)據(jù)透視表,為此,我需要使用AWS_CONTAINER_CREDENTIALS_RELATIVE_URI。
根據(jù)與該IAM相關(guān)的權(quán)限,現(xiàn)在應(yīng)該有機(jī)會(huì)繞過AWS環(huán)境。
上述步驟可自動(dòng)化實(shí)現(xiàn),并且只需要一個(gè)反向shell即可完成,但是,請(qǐng)記住,你需要保持正常的構(gòu)建環(huán)境。請(qǐng)注意,大多數(shù)構(gòu)建環(huán)境會(huì)在30-60分鐘后自動(dòng)刪除。
緩解措施
在這種情況下,修復(fù)非常簡單,永遠(yuǎn)不要將Docker守護(hù)進(jìn)程綁定到所有接口上。從包裝腳本中刪除--host=tcp://0.0.0.0:2375 行也可以來修復(fù)這個(gè)漏洞。另外,不需要綁定到TCP端口,因?yàn)閡nix套接字已經(jīng)通過了--host=unix:///var/run/docker.sock映射。
總結(jié)
雖然容器策略提供了一個(gè)很好的機(jī)制,來讓你創(chuàng)建運(yùn)行不受信任代碼的安全環(huán)境,而不需要額外的虛擬化過程。然而,這些容器的安全性與它們的配置一樣。默認(rèn)情況下,它們非常安全,但只需一個(gè)小的配置錯(cuò)誤就可以讓整個(gè)安全環(huán)境崩塌。
-
微服務(wù)
+關(guān)注
關(guān)注
0文章
139瀏覽量
7371 -
Docker
+關(guān)注
關(guān)注
0文章
489瀏覽量
11888
原文標(biāo)題:Docker容器構(gòu)建過程的安全性分析
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論