歡迎光臨
每天分享高質量文章

Docker容器構建過程的安全性分析

來源:嘶吼專業版

ID:Pro4hou

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對映。

總結

雖然容器策略提供了一個很好的機制,來讓你建立執行不受信任程式碼的安全環境,而不需要額外的虛擬化過程。然而,這些容器的安全性與它們的配置一樣。預設情況下,它們非常安全,但只需一個小的配置錯誤就可以讓整個安全環境崩塌。

《Linux雲端計算及運維架構師高薪實戰班》2018年08月27日即將開課中,120天衝擊Linux運維年薪30萬,改變速約~~~~

    *宣告:推送內容及圖片來源於網路,部分內容會有所改動,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。

    – END –


    更多Linux好文請點選【閱讀原文】

    ↓↓↓

    贊(0)

    分享創造快樂