云計算時代容器安全綜述-容器的隔離性安全風險(下)

筆者在前邊系列文章中詳細的闡述過在Docker上創(chuàng)建容器實例的過程,當我們在命令行運行docker run的時候,背后的原理是客戶端將創(chuàng)建容器實例的命令發(fā)送給docker deamon,deamon負責執(zhí)行必要的系統(tǒng)調(diào)用(比如創(chuàng)建新的命名空間等)來具體的創(chuàng)建容器進程。執(zhí)行創(chuàng)建命名空間這樣的操作需要root權(quán)限,因此我們一般會簡單的表述為創(chuàng)建容器實例需要root權(quán)限。

但是前邊的表述不是太準確,特別是在安裝了Docker的機器上,用戶并不是必須持有root權(quán)限才能創(chuàng)建容器實例,本質(zhì)上用戶只要加入docker組(docker group)后就有權(quán)限給Docker socket發(fā)送命令,比如發(fā)送docker run命令給socket來驅(qū)動docker deamon來創(chuàng)建容器實例。筆者這里需要特別強調(diào)的是將用戶加入到docker group等同于root權(quán)限,因為如果用戶有權(quán)限啟動容器實例,就意味著用戶啟動了以root權(quán)限運行的容器實例,并且這個容器實例可以在啟動的時候通過參數(shù):docker run -v /:/host <鏡像ID> 來掛載操作系統(tǒng)的根目錄,通過這點相信大家能體會到“等同”的含義。

容器實例默認情況下以root權(quán)限運行會造成重大的安全風險,因此最近幾年Rootless容器模式開始出現(xiàn)。具體來說Rootless容器模式就是讓非root用戶能夠在操作系統(tǒng)上創(chuàng)建容器實例。從技術(shù)原理的角度看,Rootless容器模式依賴于user命名空間機制來實現(xiàn),非root權(quán)限的用戶ID會被映射到容器中的root賬號,特別是當出現(xiàn)容易逃逸攻擊的時候,容器中的賬號逃逸出來后,在宿主機上就是普通賬號,因此能夠造成的危害也非常有限,因此從這一點看,Rootless容器模式對安全有巨大的提升。

咱們在上篇文章中展示過如何通過把非root賬戶加入到docker組中來啟動容器實例,也特別強調(diào)過如果運行時是podman,并且做過docker和podman的別名,那么非root賬戶無法啟動容器實例,背后的原因是podman的具體實現(xiàn)機制叫deamonless。從deamonless這個單詞讀者也應(yīng)該能夠猜到podman沒有Docker機制中的Deamon進程,由于創(chuàng)建容器實例需要創(chuàng)建命名空間,因此非root賬戶無法直接在podman運行時上啟動容器實例。

雖說Rootless這種機制聽起來很“安全”,但是它也不是銀彈,并不是所有的容器鏡像都可以成功在rootless容器模式下啟動并運行起來,原因是操作系統(tǒng)權(quán)限(capabilities)的實現(xiàn)機制和命名空間機制有點微妙。如果讀者查看Linux操作系統(tǒng)的文檔,會發(fā)現(xiàn)user命名空間不僅僅隔離了用戶(user)和組(group),還包括內(nèi)核的權(quán)限(capabilities)。大白話說就是我們可以為運行在某個特性user命名空間的進程增加或者刪除某項操作系統(tǒng)內(nèi)核訪問的能力,這個能力只限于這個命名空間。對于rootless容器實例來說,當我們給rootless容器實例增加了某項內(nèi)核訪問權(quán)限,這個權(quán)限只局限于容器進程內(nèi),并不等同于容器實例訪問宿主機上的內(nèi)核資源。

筆者承認前邊的這句話并沒有把rootless和user命名空間之間微妙的關(guān)系說清楚,咱們還是繼續(xù)舉個例子說明。CAP_NET_BIND_SERVICE這個能力在Linux操作系統(tǒng)內(nèi)核中允許進程可以使用低于1024的端口號。假設(shè)我們在Docker環(huán)境中啟動一個容器實例(默認情況下容器實例以root權(quán)限運行,因此持有CAP_NET_BIND_SERVICE內(nèi)核權(quán)限),并且和宿主機共享網(wǎng)絡(luò)命名空間,那么容器實例中運行的應(yīng)用程序就可以使用1-65536所有的端口號。反過來如果我們運行rootless容器,顯示的賦予CAP_NET_BIND_SERVICE權(quán)限并和宿主機共享host命名空間,那么運行在容器中的相同springboot應(yīng)用是無法使用低于1024的端口號,這就證實了我們前邊的邏輯,內(nèi)核能力只限于容器內(nèi)部,無法透出到宿主機。

雖說內(nèi)核能力和user命名空間這種細微的設(shè)計看似罪惡之源,但是從安全的角色,這是健壯的設(shè)計,我們不用擔心通過user命名空間隔離的進程修改宿主機操作系統(tǒng)內(nèi)核的配置,造成系統(tǒng)運行或者重啟等故障。筆者對rootless這種模式的使用經(jīng)驗顯示,大部分容器鏡像都可以成功運行,讀者的場景如果對安全要求非常高,建議考慮rootless這種模式。

rootless容器實例從宿主機的角度看,userid為普通用戶,雖然從容器內(nèi)部看的確以root賬戶運行,這種不同角度看到不同的用戶賬戶權(quán)限會造成容器內(nèi)部訪問文件系統(tǒng)數(shù)據(jù)的問題,因此需要操作系統(tǒng)的文件系統(tǒng)類型支持文件ownership和group ownership的remap,這部分內(nèi)容要延展開會非常復(fù)雜,感興趣的讀取可以自行參考相關(guān)材料。

不過rootless容器模式相比于Docker容器,略顯青澀,還處于非常初級的發(fā)展階段,目前支持比較完整的運行時包括runc和podman,docker雖說也支持,但是還處于試驗階段。從Kubernetes的角度看,目前尚未支持rootless容器這種模式,相信社區(qū)已經(jīng)在努力朝著這個方向發(fā)展,感興趣的讀者可以參考Akihiro Suda等人開發(fā)的一個POC的例子,讀者可以自行尋找。

安全是個全局的話題,因此運行時的問題并不是全部,筆者的經(jīng)驗顯示大部分安全問題都和配置有關(guān),容器以root權(quán)限運行疊加配置的風險,這是容器安全領(lǐng)域風險的主要來源。因此在實際的項目中,大家需要仔細分析自己的業(yè)務(wù)場景,結(jié)合本文提到的user id重寫以及rootless容器模式,來規(guī)避容器實例以root權(quán)限運行的問題。

說到容器實例以root權(quán)限運行,Docker和很多其他的容器運行時都提供了容器實例啟動的時候指定--privileged參數(shù),如果你不知道這個參數(shù),請忽略后續(xù)的信息,因為這個--privileged參數(shù)被稱作是“計算機歷史上最臭名昭著的參數(shù)”。這么叫的原因有兩個,其一是很強大,其二是很容易被濫用造成安全風險。

由于安全總是被誤解為安全專家的職責范圍,因此很多時候運維人員和系統(tǒng)開發(fā)人員對安全的意識并不強,導(dǎo)致很多人對--privileged這個參數(shù)的理解是:容器實例以root權(quán)限運行,相信大家應(yīng)該清楚默認情況下Docker平臺下啟動的容器實例都是以root權(quán)限運行這個事實,因此讀者讀到這里的疑問是:這個--privileged參數(shù)到底有啥用?

在繼續(xù)討論之前,大家需要了解一個技術(shù)細節(jié),雖說默認情況下容器實例以root賬號運行,但是操作系統(tǒng)內(nèi)核的設(shè)計人員關(guān)于虛擬化這套機制也考慮了安全因素,具體來說就是容器實例運行的這個root權(quán)限也只持有部分操作系統(tǒng)的內(nèi)核調(diào)用能力(capabilities),咱們接下來實際的例子來看看,默認情況下容器實例持有的操作系統(tǒng)內(nèi)核權(quán)限以及攜帶--privileged的場景下權(quán)限清單。

筆者在自己的環(huán)境中分別運行不帶--privileged和帶--privileged的場景,通過capsh輸出(這個工具只有在Linux操作系統(tǒng)有)權(quán)限能力清單,詳細信息如下:

?? source docker run --rm -it alpine sh -c 'apk add -U libcap; capsh --print | grep Current'

...

Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,

cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,

cap_audit_write,cap_setfcap+eip

?? source docker run --rm -it --privileged alpine sh -c 'apk add -U libcap; capsh --print | grep Current'

...

Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,

cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,

cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,

cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,

cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,

cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,

cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,

cap_wake_alarm,cap_block_suspend,cap_audit_read+eip

從輸出的清單大家應(yīng)該很明顯就能看出,攜帶--privileged的容器進程會持有幾乎全部操作系統(tǒng)內(nèi)核的訪問權(quán)限,即便是這里邊大部分容器進程都用不著。讀者可能會問,既然這個--privileged參數(shù)如此具備殺傷力,為啥要引入這個參數(shù)呢?

具體來說--privileged用來實現(xiàn)Docker in Docker模式,自己搭建過流水線CICD的同學(xué)應(yīng)該理解DID這種模式,Jenkins運行在容器中,但是我們需要在容器中來訪問deamon構(gòu)建容器鏡像,雖然說這種模式很普遍,讀者還是建議大家謹慎對待--privileged參數(shù)。如果讀者的業(yè)務(wù)場景的確需要使用--privileged參數(shù),建議做好日志記錄和安全審計,并且對需要使用--privileged參數(shù)的場景要多次推敲討論,降級這個參數(shù)造成的安全風險。

另一種思路就是給應(yīng)用所需的權(quán)限,雖然說這個看起來很自然,但是由于大部分應(yīng)用會依賴很多三方包,系統(tǒng)包,因此應(yīng)用程序在靜態(tài)的時候,很難完整確認程序運行所需要的所有內(nèi)核權(quán)限。因此我們需要借助于Tracee這樣的工具來追蹤記錄應(yīng)用程序需要的內(nèi)核權(quán)限。咱們還是通過下邊的例子來說明這個工具:

在自己的Linux操作系統(tǒng)上首先安裝這個工具(如果沒有的話),接著打開兩個終端,在終端1運行 docker run -it --rm nginx。

$ docker run -it --rm nginx

然后在終端2上運行tracee工具,并設(shè)置對應(yīng)的參數(shù),如下:

$ ./tracee.py -c -e cap_capable

TIME(s) UTS_NAME UID EVENT COMM PID PPID RET ARGS

125.000 c8520fe719e5 0 cap_capable nginx 6 1 0 CAP_SETGID

125.000 c8520fe719e5 0 cap_capable nginx 6 1 0 CAP_SETGID

125.000 c8520fe719e5 0 cap_capable nginx 6 1 0 CAP_SETUID

124.964 c8520fe719e5 0 cap_capable nginx 1 3500 0 CAP_SYS_ADMIN

124.964 c8520fe719e5 0 cap_capable nginx 1 3500 0 CAP_SYS_ADMIN

通過這種方式我們就能準確的知道應(yīng)用程序需要的內(nèi)核訪問權(quán)限,然后就可以通過命令行$ docker run --cap-drop=all --cap-add=<cap1> --cap-add=<cap2> <image> ...來指定合適的權(quán)限集合。大家需要注意的是我們首先要cap-drop=all,然后再設(shè)定應(yīng)用程序需要的操作系統(tǒng)權(quán)限。

容器默認情況下以root權(quán)限運行只是安全風險中的一環(huán),我們在啟動容器實例的使用,也可以使用-v選項來將宿主機的目錄掛載到容器中,用來實現(xiàn)宿主機和容器之間進行數(shù)據(jù)文件的共享。雖然說這種方式很方便就可是實現(xiàn)數(shù)據(jù)共享,但是也存在巨大的安全隱患,并沒有人阻止你把宿主機的根目錄掛載到容器中,比如下邊的例子。

筆者在本地啟動了一個容器實例,并把宿主機(macOS的虛擬機)的根目錄掛載到容器中,大家從輸出的結(jié)果可以看到,從容器實例中可以看到宿主機的系統(tǒng)目錄:

?? source docker run -it -v /:/hostroot ubuntu bash

root@59c908045783:/# ls

bin? boot? dev? etc? home? hostroot? lib? lib32? lib64? libx32? media? mnt? opt? proc? root? run? sbin? srv? sys? tmp? usr? var

root@59c908045783:/# cd hostroot

root@59c908045783:/hostroot# ls

A? ? ? ? ? ? C? F? I? L? ? ? ? N? Q? System? Users? ? W? Z? bin? cores? e? ? g? ? host_mnt? k? ? lib64? mnt? opt? ? ? proc? root? sbin? t? ? usr? w? z

Applications? D? G? J? Library? O? R? T? ? ? V? ? ? ? X? a? boot? d? ? ? etc? h? ? i? ? ? ? l? ? m? ? ? n? ? p? ? ? ? q? ? run? srv? tmp? v? ? x

B? ? ? ? ? ? E? H? K? M? ? ? ? P? S? U? ? ? Volumes? Y? b? c? ? dev? ? f? ? home? j? ? ? ? lib? media? o? ? private? r? ? s? ? sys? u? ? var? y

root@59c908045783:/hostroot#

從輸出的信息可以看到,如果惡意攻擊者攻破了容器實例,那么整個宿主機就直接暴露給黑客,他的確可以干任何想干的事情。雖然說在容器掛載宿主機的根目錄屬于這種場景比較牽強,但是這種的場景的變體我們?nèi)粘5南到y(tǒng)開發(fā)和運維中可能會遇到,包括:

- 在容器中掛載/etc會允許惡意攻擊修改系統(tǒng)的用戶信息,特別是/etc/passwd文件中包含了用戶密碼信息

- 掛載/bin文件夾會給惡意攻擊者寫入惡意可執(zhí)行文件的機會

- 掛載宿主機的日志文件后,惡意攻擊者可以通過篡改日志來抹掉攻擊系統(tǒng)的證據(jù)

- 在Kubernetes場景下,將/var/log文件夾關(guān)在到容器中,所有用戶都可以通過kubectl logs訪問系統(tǒng)的日志信息

在Docker的場景下,docker deamon通過socket /var/run/docker.sock來接收客戶端的命令,所有有權(quán)限寫這個sock的客戶端,理論上都可以創(chuàng)建容器實例。并且通過前文我們知道,deamon以root權(quán)限運行,會執(zhí)行任何發(fā)過來的指令。因此我們可以將有權(quán)限訪問Docker socket等同于宿主機上的root權(quán)限。

最后,我們也要對容器實例和宿主機共享命名空間的場景保持極高的警惕性,比如說我們希望容器實例訪問宿主機上的進程信息,那么就需要在容器啟動的時候,指定參數(shù)--pid=host。這種場景下從容器內(nèi)部就可以看到所有的宿主機上的進程,plus所有的容器進程,因此我們就可以在容器實例中通過kill命令來結(jié)束某些進程,這會造成巨大的安全風險。

不過在容器之間,或者容器和宿主機之前共享某些命名空間并不總是bad idea,邊車模式正是采用了共享網(wǎng)絡(luò)命名空間的機制,來將網(wǎng)絡(luò)相關(guān)的功能拆分到代理POD上。具體來說Service mesh邊車代理負責應(yīng)用程序的網(wǎng)絡(luò)接入功能,比如HTTPS的證書管理和配置,這樣就可以讓應(yīng)用程序更聚焦于業(yè)務(wù)邏輯處理,而邊車POD負責設(shè)置TLS證書等。

另外我們也可以在邊車上收集進入應(yīng)用程序的請求信息,一次來作為監(jiān)控,日志等平臺的數(shù)據(jù)來源;筆者也見過在邊車代理上做安全策略控制的場景,我們可以在邊車代理上監(jiān)控所有的進出流量以及關(guān)鍵字,以此來增強運行在容器實例中應(yīng)用的安全性。

好了,這篇文章就這么多了,咱們下篇文章來介紹容器網(wǎng)絡(luò)安全,敬請期待!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容