SELinux 也是實現了強制存取控制 MAC (mandatory access control) 的訪問控制安全策略機制。但相對於 AppArmor 來說學習門檻真的高蠻多的,這邊就只能就我目前初學的心得做說明,並且用 SELinux + Docker 的方式作為切入點。
在 SELinux 的世界中沒有所謂的 root 使用者的概念,取而代之的只有所謂的"安全本文(security contexts)",而安全本文可以標記程序、檔案類型的資源。透過常用指令帶上 Z 這個參數就可以看到所屬的安全文本。
#確認 SELinux 有啟動
sestatus ;
# 直接進容器觀察看看
docker run -it --rm aeifkz/my-ubuntu:v1.0 bash ;
# 看看目前容器內執行程序所帶的 security contexts
# system_u:system_r:spc_t:s0
ps Z ;
# 看看目前容器內檔案所帶的 security contexts
# system_u:object_r:container_share_t:s0
ls -Z ;
exit ;
那接下來要針對這個 security contexts 做說明。這部分可以參考 第三章、SELinux 初探,格式為[SELinux用戶別:角色:類型]。而現行主要的限制是針對 "類型" 這個欄位進行限制,SELinux 主要的限制方式就是針對這些欄位去定義可以進行的動作權限。但也因為如此複雜度會比 AppArmor 來的高一些。
這樣看起來 SELinux 是不是蠻簡單的,只要搞清楚這些 security contexts (為了不要講這麼多字,我後面都會用 label(標籤) 稱呼它們) 的定義方式以及對應關係就好了。但事情會遠比我們想像的更複雜,這個等到最後面再來說明。但待會先從不受標籤限制的容器操作開始測試起,這邊先建立一個普通容器,然後掛載硬碟裝置並且對宿主機進行檔案寫入。
# 建立一般容器,但是掛載根目錄
docker run -it --rm -v /:/mnt aeifkz/my-ubuntu:v1.0 bash ;
# 觀察一下跟剛剛有甚麼差別,應該是沒有才對
ps Z ;
chroot /mnt ;
echo "I am root" > /root/I_am_root ;
# 離開 chroot 狀態
exit ;
這邊我要開始思考一件事情,為什麼特權容器可以對宿主機的 /root 進行寫入? 這件事情在 DAC 的存取模式下在正常不過,因為身分是 root 而且 /root 是擁有者可讀、可寫、可執行。但現在我們要開始用 MAC 的想法進行分析,究竟當下的程序的標籤是甚麼、對象的標籤是甚麼以及究竟這兩個標籤能不能互相讀取。
# 在宿主機確認 SELinux 相關工具都有裝好
sudo yum install setools-console attr -y ;
sudo yum install setroubleshoot-server -y ;
# 確認一下 /root 的資料夾標籤下檔案的標籤
# system_u:object_r:admin_home_t:s0
sudo ls -Z /root/ ;
# 透過 sesearch 指令去搜尋允許的規則
sesearch -s spc_t -t admin_home_t --allow -c file ;
這個標籤的開放格式可以參考 SELinux/Tutorials/How SELinux controls file and directory accesses 裡面的內容,其中有提到如下。
allow auditd_t auditd_log_t:file { write };
Well, this is exactly what SELinux does:
1. if the process runs within the auditd_t domain (and thus has a security context with auditd_t in its third position)
2. and if the target has the type auditd_log_t set,
3. and the target is a file
4. then the permission write is granted
所以依照剛剛得到唯一正常的格式如下,感覺我們應該是找錯了對吧?
allow files_unconfined_type file_type : file { ioctl read write create ...略 } ;
這是我當遇到的瓶頸,也是我覺得 SELinux 門檻高的原因,找不到一條實際的規則去對應到存取的部分。撞牆撞了很久偶然看到這篇 SELinux type attributes provide this grouping functionality 瞬間豁然開朗。原來 SELinux 為了設定方便,它有支援群組的功能,也就是可以把一群標籤定義成一個群組A,把另一群標籤訂為群組B,然後就可以定義群組A 跟群組B 的存取關係。
# -a 找尋屬性名稱,後面接要查詢的屬性
# 所以發現剛剛那條規則的確有定義到我們的存取行為
seinfo -afiles_unconfined_type -x | grep spc_t ;
# 實測下來這個 file_type 的標籤涵蓋了所有檔案類的標籤
seinfo -afile_type -x | grep admin_home_t ;
回到容器安全上面,到目前為止我們都沒有讓真正的 SELinux 標籤套用到 docker 身上,因為 spc_t 這個標籤感覺權限挺大的。接著來設定 docker 套用 SELinux 設定,步驟如下 :
{
"selinux-enabled": true
}
sudo systemctl restart docker ;
docker info | grep Security -A3 ;
# 建立一般容器,但是掛載根目錄
docker run -it --rm -v /:/mnt aeifkz/my-ubuntu:v1.0 bash ;
# 觀察一下跟剛剛有甚麼差別
# system_u:system_r:container_t:s0:c818,c852
ps Z ;
chroot /mnt ;
# 試著寫入 /root 結果失敗
echo "I am root" > /root/I_am_root ;
# 離開直到回到宿主機
exit ;
# 沒看到甚麼寫入相關權限,估計是沒辦法做寫入的
sesearch -s container_t -t admin_home_t --allow -c file ;
# 找一下 container_t 可以寫入哪種標籤的檔案
sesearch -s container_t --allow -c file ;
# 暫時將這個檔案的標籤改成 container_file_t
sudo chcon -t container_file_t /root/I_am_root ;
# 建立一般容器,但是掛載根目錄
docker run -it --rm -v /:/mnt aeifkz/my-ubuntu:v1.0 bash ;
chroot /mnt ;
# 試著寫入 /root ,現在就會成功惹
echo "I am root" > /root/I_am_root ;
# 離開直到回到宿主機
exit ;
那假如今天真的需要把宿主機的資料夾掛載進來的話要怎麼用呢? 參考 docker selinux-enabled作用可以知道掛載目錄時加上 Z 跟 z 則會調整掛載目錄的標籤內容,使用 Z 則是會在標籤中加上 category,使用 z 則不會加入,這部分決定了 docker 容器內是否可以共享那個資料夾。當然也不是所以的資料夾都可以進行掛載,這部分還是有限制存在的,因為它背後的原理也是幫忙修改掛載目錄的標籤,但在 SELinux 的世界標籤也步是想改就能改的。
# 建立一般容器,但是掛載根目錄並加上 z,會整個被拒絕
docker run -it --rm -v /:/mnt:z aeifkz/my-ubuntu:v1.0 bash ;
# 重新建立一個資料夾進行掛載
mkdir ~/selinux_test ;
ls -Z ~/ ;
# 改掛載剛剛建立的資料夾,則可以掛載成功
docker run -it --rm -v ~/selinux_test:/mnt:z aeifkz/my-ubuntu:v1.0 bash ;
# 離開直到回到宿主機
exit ;
# 會發現該資料夾的標籤被改寫,也就是我們剛剛上個階段在做的事情
ls -Z ~/ ;
最後我們一樣來探討 selinux 實質上提供了甚麼防護呢? 這部分列舉攻擊篇的手法來進行驗證。
攻擊手法 | selinux 預設能否阻擋? |
---|---|
privileged + host pid | N |
--cap-add=ALL + host pid | N (作業9) |
privileged | N |
(CVE-2022-0492) unshare + 特權逃逸手法 | NA(不適用) |
安裝 linux_module | Y |
docker.sock 掛載 | Y |
作業9 : 跟當初驗證 Apparmor 機制一樣,但假如給予今天給予容器所有的能力並掛載 host pid,到底是否能夠順利逃逸呢? 假如可以的話,驗證一下為何 SELinux 無法防守住。
本日總結 :