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