天天看點

(CVE-2023-25173 原理分析)Linux 容器中的漏洞 – 研究和緩解

(CVE-2023-25173 原理分析)Linux 容器中的漏洞 – 研究和緩解

翻譯自:https://www.benthamsgaze.org/2022/08/22/vulnerability-in-linux-containers-investigation-and-mitigation/

涉及名詞概念(為行文簡潔可能會部分會采取簡稱)

negative group:負組

primary group:主要組

supplementary groups:補充組

set-group programs:設定組程式(亦有人譯做集合組,筆者認為設定組更為貼切一些)

set-user programs:設定使用者程式

Open Container Initiative :開放容器計劃

在作業系統中,通路控制機制被用來限制哪些程式可以打開哪些檔案,其存在的時間幾乎與計算機本身存在的時間一樣長。即便是現在,通路控制仍然被廣泛使用,與加密保護檔案相比,通路控制更加靈活和高效。雖然通路控制機制的曆史非常悠久,但其仍在不斷創新,尤其是現在在容器中,如Docker和Kubernetes以及雲提供商提供的類似技術。就目前而言,服務大多不是隻在一台計算機上運作大量軟體,而是拆分為在容器中運作的微服務。每個容器都與同一台計算機上的其他容器隔離,就好像它有自己的計算機和作業系統一樣,并且被阻止讀取其他容器中的檔案。*但是,實際情況是隻有一個作業系統,容器運作時會給人一種建立并存在多個作業系統的錯覺。在容器運作時,其内部也應設定相應的通路控制機制(後文均簡寫為通路控制),因為并非容器内運作的每個程式都被允許能夠通路每個檔案。也可以向多個容器授予對同一個目錄的通路權限,并使用通路控制來限制每個容器可以對目錄内容執行的操作。如果通路控制沒有正常工作,那麼攻擊者可能會讀取或修改他們不應該讀取或修改的檔案。不幸的是,确實存在這樣的漏洞。壞消息是,它源于規範中的遺漏,所有主流容器運作時的都會遵循該規範,是以無論您使用哪個容器工作(例如runc,crun,Kata Containers),也無論您直接使用容器(例如通過Docker或podman)還是間接使用容器(例如通過Kubernetes)),其都有可能存在這樣的漏洞。好消息是,該漏洞影響了 Linux 通路控制權限中未廣泛使用的功能 - 負組權限(negative group)。但是,如果您的系統确實依賴于此功能,則漏洞可能很嚴重。請繼續閱讀,您将獲得關于該漏洞存在的原因以及可以采取哪些措施來緩解問題的更為詳細資訊。

Linux 權限簡介

在Linux當中,存在使用者帳戶,且每個使用者也是一個組的成員。每個對象(檔案、目錄、裝置等)都有一個與其關聯的所有者和所屬組。該對象還具有一組與以下三個類相關聯的權限:所有者、所屬組和其他。這些權限告訴作業系統使用者是否被允許從該對象讀取内容 (r)、寫入内容 (w) 以及執行該對象 (x)。如果某使用者是該對象的所有者,則使用所有者類權限,如果某使用者是該對象所屬組的成員,則使用組類權限,否則使用其他類權限。

例如,包含公司财務資料庫的檔案可能由首席财務官 (CFO) 擁有,并具有所有者類權限“r+w”。它可以将該檔案的組設定為“審計員”,組類權限僅為“r”,而其他類權限設定為無。然後,首席财務官可以自由地讀寫資料庫,集團審計員的所有成員都可以讀取資料庫,其他人根本無法通路資料庫。

這種配置是有效的,但很不靈活。如果審計員也是公司運動隊的成員,并且運動隊使用組類權限來控制對即将到來的賽程資料庫的通路,會發生什麼情況?審計員是否必須離開審計員組并加入運動隊組,進而失去對帳戶資料庫的通路權限?實際上,否,因為使用者不僅具有主要組(primary group),而且可以具有補充組(supplementary groups)。當進行通路控制決策時,如果被通路對象的組與使用者的任何組比對,則作業系統将使用組類權限。是以,使用者可以是稽核員組和運動隊組的成員。使用者不僅可以擷取其任何補充組的權限,還可以将他們擁有的任何檔案的組更改為其任何補充組。

我需要介紹的最後一個概念是設定組程式(set-group programs)。假設我們不希望每個使用者都能夠看到财務資料庫,但我們同時也希望每個使用者都能夠獲得其部門的餘額報告。我們可以建立一個程式來檢查哪個使用者啟動了該程式并擷取該使用者的餘額報告。但這顯然行不通,因為被運作程式會繼承運作它們的使用者的權限,而該使用者無法讀取資料庫。是以,我們将程式設定組設定為“審計員”,那麼現在當它運作時,它将承擔審計員組的權限并能夠讀取财務資料庫。(設定使用者程式(set-user programs)也有類似的功能,但我不會在這裡讨論)。

負組權限

在上面的示例中,所有者類權限 (r+w) 大于所屬組類權限 (r),而所屬組又大于其他類(無)。通路控制通常的限制方式是這樣的:更為具體的類至少擁有與不太具體的類一樣多的權限。但是,這不是固定不變的。例如,如果将對象設定為所有者類:r、所屬組:無和 其他:r,那麼就是除對象所屬組的成員外,每個人都可以讀取此檔案。這是 Linux 權限的一個有意功能,并且确實被使用,盡管使用的不是很頻繁。它可能有用的地方是為敏感對象建立拒絕清單,以便令某些不可信的程式無法通路它。

為了支援此類方案,使用者應當不能删除其所在的組,因為一旦删除該使用者就可以繞過負組權限強制實施的通路控制。那麼可以通過設定屬組可執行檔案來執行此操作嗎?比如某使用者建立一個程式,将其所屬組更改為使用者的補充組之一,然後運作此程式。當程式運作時,其屬組将是該使用者的補充組,而不是該使用者的主要組。**那麼在這樣情況下,該使用者是否可以删除主組并通路其組不允許的對象呢?

幸運的是,該使用者并不能通路。因為當使用者登入時,其主要組将被複制并添加到補充組清單中。當設定組程式運作時,校驗的組确實是其補充組,但補充組清單包括主要組。是以,作業系統将拒絕通路請求,因為(原本)被拒絕通路的主要組位于補充組清單中。但是,如果設定組程式在容器中運作,那麼情況将會有所不同。

主組在容器中不備份

在登入系統時備份(添加)使用者主要組到補充組清單的行為是在 1994 年釋出的4.4BSD 作業系統中引入的,Linux 采用了類似的方法。但是,Docker 容器在設定主組時并沒有遵循這一點,是以主要組不會被備份在補充組清單中出現。開放容器計劃為容器建立了一個可互操作的标準,但該标準沒有明确說明容器應該做什麼。甚至,就我所知道的大多數容器在實作時都效仿了 Docker 。是以,在容器中運作的程式可能會删除其主要組,如果恰好正在使用負組權限,則程式将能夠違反系統的通路控制政策。

利用此漏洞

如果攻擊者可以在容器中運作代碼,則可以建立一個設定組程式,并将該組設定為攻擊者的一個補充組。當此程式運作時,其組将是攻擊者的補充組,補充組清單将是該使用者的補充組。但是,使用者的主要組并不會出現在該程式的補充組清單當中。如果該程式通路原本對攻擊者的主要組具有負組權限的檔案,它将成功通路該檔案,而這是違反負組權限預期的

可以在下面的視訊中找到此漏洞的示範,該視訊使用了我在 Github 上提供的POC(https://github.com/sjmurdoch/permission-experiment)。我建立的容器鏡像位于 Docker Hub (https://hub.docker.com/repository/docker/sjmurdoch/permission-experiment/)上,适用于 x86_64 和 aarch64。

緩解問題

我在2022 年 5 月 27日向 Docker 報告了此漏洞,但對此漏洞的研究已傳遞給 Moby 項目,因為它影響的不僅僅是 Docker ,還包括多個Linux 容器。 受影響軟體的開發人員一直在開發更新檔,以修複漏洞并解決規範中的遺漏之處。由于該漏洞僅影響不常用的通路控制功能,是以該漏洞的嚴重性被評估為較低,是以可以在出現緩解方法後公開讨論該漏洞。

對于受到影響的軟體包已配置設定CVE來跟蹤該漏洞。為什麼有多個 CVE ID?因為開放容器計劃運作(Open Container Initiative Runtime)規範并不要求存在這種易受攻擊的機制,但由于規範太過于模糊,于是乎便允許了這種行為的存在。盡管目前此規範的所有實作都容易受到攻擊,但 CVE ID 也隻是與易受攻擊的軟體相關聯,而不是連接配接到規範。我希望規範也會被修改,用明确要求實作強制執行正确的行為。到目前為止配置設定的 CVE ID 為:

  • CVE-2022-2989: podman
  • CVE-2022-2990: buildah
  • CVE-2022-2995: cri-o
  • CVE-2022-36109: Moby (Docker Engine)

但是,與此同時,可以采取一些步驟來解決該漏洞。

使用“su”初始化容器

該漏洞是因為容器運作時未正确設定在容器配置中指定的組(即User Dockerfile指令和runAsUser/runAsGroup Kubernetes設定)。

存在一種方法是以root使用者身份啟動容器,然後使用帶有“-l”标志的“su”指令來設定使用者。這種方法将正确地建立組,進而避免問題。

手動備份(添加)組

容器運作時使用/etc/passwd和/etc/group的内容分别設定主組和輔助組。

如果您修改/etc/group以第二次将使用者添加到它們的主組中,容器運作時将正确地設定這些組。

這種方法似乎有點老生常談,但确實很管用。

例如,在這裡,使用者的主組同時在/etc/passwd(帶有數字組ID 1000)和/etc/group中指定,其中該使用者也被列為成員。

/etc/passwd:

user❌1000:1000:Linux User,,,:/home/user:/bin/ash

/etc/group:

user❌1000:user

阻止setuid/setgid程式

該漏洞依賴于具有更改可用權限的設定組程式。

如果您不需要setgid或setuid程式,您可以通過Docker“--Security-opt no-new-vilileses”标志或Kubernetes“AllowPrivilegeEscalation=False”設定禁用此功能。

識别那些對象使用了負組權限

很難知道軟體是否依賴于負組權限,因為這是一個細節,通常沒有明确的文檔記錄。

在研究開始時,我編寫了一個程式來搜尋具有負組權限的檔案和目錄(https://github.com/sjmurdoch/permission-experiment/tree/main/scanperms)。

它并不完美-它不會識别為臨時檔案設定負組權限的情況。

它也不會發現在配置檔案中(例如,對于sudo)而不是在檔案系統上實作負組權限的情況,也可能存在誤報。

系統設計的經驗教訓

當然了,這個漏洞在被修複的同時,我們也可以從中吸取一些更廣泛的教訓。

首先 ,就本次而言,将使用者的主要組包括在補充組中并沒有作為一項要求寫下來的-它是未定義(具體内容)的。而“未定義”的意思是應用程式不依賴于特定的行為,但這也不意味着所有的選擇都是同樣正确的。

其次 ,在互操作性标準(Interoperability standards)中有這樣一個問題:它們規定了最低限度來實作最大程度的靈活性,但其安全性顯然并不是您想要的。EMV卡支付互操作性标準也存在同樣的弱點。

是以對于這種現狀,無論是出于安全或其他原因都應該要求出具具體标準去規範其行為。

另外,還有一些關于軟體重寫的課題,比如目前為了確定記憶體安全而流行采用Rust語言去重寫C語言程式。這樣做确實可以帶來實質性的安全好處,但任何重寫都有可能丢失某些基本功能,特别是當這些基本功能隻存在遺留代碼當中時。比如,Docker有效地采用Go重寫了組的初始化代碼,也确實避免了原本C中的許多潛在缺陷,但由于沒有采用與Linux相同的方式實作登入序列,是以又建立了一個新的漏洞。

當然,這并不是說重寫是一個壞主意,隻是這樣做可能會帶來新的風險,應該注意減輕。

繼續閱讀