Linux Capabilities 入門教程:基礎實戰篇
該系列文章總共分為三篇:
Linux Capabilities 入門教程:概念篇
Linux Capabilities 入門教程:基礎實戰篇
待續...
上篇文章介紹了 Linux capabilities 的誕生背景和基本原理,本文將會通過具體的示例來展示如何檢視和設定檔案的 capabilities。
Linux 系統中主要提供了兩種工具來管理 capabilities:libcap
和 libcap-ng
。libcap
提供了 getcap
和 setcap
兩個命令來分別檢視和設定檔案的 capabilities,同時還提供了 capsh
來檢視當前 shell 程序的 capabilities。libcap-ng
filecap
來檢視和設定 capabilities。
1. libcap
安裝很簡單,以 CentOS 為例,可以通過以下命令安裝:
$ yum install -y libcap
如果想檢視當前 shell 程序的 capabilities,可以用 capsh
命令。下面是 CentOS 系統中的 root 使用者執行 capsh
的輸出:
$ capsh --print 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+ep Bounding set =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 Securebits: 00/0x0/1'b0 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) uid=0(root) gid=0(root) groups=0(root)
解釋一下:
Current : 表示當前 shell 程序的 Effective capabilities 和 Permitted capabilities。可以包含多個分組,每一個分組的表示形式為
capability[,capability…]+(e|i|p)
,其中e
表示 effective,i
表示 inheritable,p
表示 permitted。不同的分組之間通過空格隔開,例如:Current: = cap_sys_chroot+ep cap_net_bind_service+eip
。再舉一個例子,cap_net_bind_service+e cap_net_bind_service+ip
cap_net_bind_service+eip
等價。Bounding set : 這裡僅僅表示 Bounding 集合中的 capabilities,不包括其他集合,所以分組的末尾不用加上
+...
。Securebits : 我也沒搞清楚這是個什麼鬼。
這個命令輸出的資訊比較有限,完整的資訊可以檢視 /proc 檔案系統,比如當前 shell 程序就可以檢視 /proc/$$/status
。其中一個重要的狀態就是 NoNewPrivs
,可以通過以下命令檢視:
grep NoNewPrivs /proc/$$/status
NoNewPrivs: 0
根據 prctl(2) 中的描述,自從 Linux 4.10 開始,/proc/[pid]/status
中的 NoNewPrivs
值表示了執行緒的 no_new_privs
屬性。至於 no_new_privs
究竟是幹嘛的,下面我單獨解釋一下。
no_new_privs
一般情況下,execve()
系統呼叫能夠賦予新啟動的程序其父程序沒有的許可權,最常見的例子就是通過 setuid
和 setgid
來設定程式程序的 uid 和 gid 以及檔案的訪問許可權。這就給不懷好意者鑽了不少空子,可以直接通過 fork 來提升程序的許可權,從而達到不可告人的目的。
為了解決這個問題,Linux 核心從 3.5 版本開始,引入了 no_new_privs
屬性(實際上就是一個 bit,可以開啟和關閉),提供給程序一種能夠在 execve()
呼叫整個階段都能持續有效且安全的方法。
開啟了
no_new_privs
之後,execve 函式可以確保所有操作都必須呼叫execve()
判斷並賦予許可權後才能被執行。這就確保了執行緒及子執行緒都無法獲得額外的許可權,因為無法執行 setuid 和 setgid,也不能設定檔案的許可權。一旦當前執行緒的
no_new_privs
被置位後,不論通過 fork,clone 或 execve 生成的子執行緒都無法將該位清零。
Docker 中可以通過引數 --security-opt
來開啟 no_new_privs
屬性,例如:docker run --security-opt=no_new_privs busybox
。下面通過一個例子來體會一下 no_new_privs
屬性的作用。
首先擼一段 C 程式碼,顯示當前程序的有效使用者 id:
$ cat testnnp.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
printf("Effective uid: %d\n", geteuid());
return 0;
}
$ make testnnp
cc testnnp.c -o testnnp
將可執行檔案打入 docker 映象中:
FROM fedora:latest
ADD testnnp /root/testnnp
RUN chmod +s /root/testnnp
ENTRYPOINT /root/testnnp
構建映象:
$ docker build -t testnnp .
Step 1 : FROM fedora:latest
---> 760a896a323f
Step 2 : ADD testnnp /root/testnnp
---> 6c700f277948
Removing intermediate container 0981144fe404
Step 3 : RUN chmod +s /root/testnnp
---> Running in c1215bfbe825
---> f1f07d05a691
Removing intermediate container c1215bfbe825
Step 4 : ENTRYPOINT /root/testnnp
---> Running in 5a4d324d54fa
---> 44f767c67e30
Removing intermediate container 5a4d324d54fa
Successfully built 44f767c67e30
下面來做兩個實驗,先在沒有開啟 no-new-privileges
的情況下啟動容器:
$ docker run -it --rm --user=1000 testnnp
Effective uid: 0
從輸出結果來看,只要給可執行檔案設定了 SUID 標識,即使我們使用普通使用者(UID=1000)來執行容器,程序的有效使用者也會變成 root。
接著在開啟 no-new-privileges
的前提下啟動容器,以防止執行設定了 SUID 標識的可執行檔案進行 UID 轉換:
$ docker run -it --rm --user=1000 --security-opt=no-new-privileges testnnp
Effective uid: 1000
可以看到,開啟了 no_new_privs
屬性之後,即使可執行檔案設定了 SUID 標識,執行緒的有效使用者 ID 也不會變成 root。這樣即使映象中的程式碼有安全風險,仍然可以通過防止其提升許可權來避免受到攻擊。
Kubernetes 也可以開啟 no_new_privs
,不過邏輯稍微複雜一點。當 Pod 的 SecurityContext
定義下的 allowPrivilegeEscalation
欄位值為 false 時(預設就是 false),如果不滿足以下任何一個條件,就會開啟 no_new_privs
屬性:
設定了
privileged=true
增加了
CAP_SYS_ADMIN
capabilities,即capAdd=CAP_SYS_ADMIN
以 root 使用者執行,即 UID=0
例如,當設定了 privileged=true
和 allowPrivilegeEscalation=false
時,就不會開啟 no_new_privs
屬性。同理,設定了 capAdd=CAP_SYS_ADMIN
和 allowPrivilegeEscalation=false
也不會開啟 no_new_privs
屬性。
管理 capabilities
可以通過 getcap
來檢視檔案的 capabilities,例如:
$ getcap /bin/ping /usr/sbin/arping
/bin/ping = cap_net_admin,cap_net_raw+p
/usr/sbin/arping = cap_net_raw+p
也可以使用 -r
引數來遞迴查詢:
$ getcap -r /usr 2>/dev/null
/usr/bin/ping = cap_net_admin,cap_net_raw+p
/usr/bin/newgidmap = cap_setgid+ep
/usr/bin/newuidmap = cap_setuid+ep
/usr/sbin/arping = cap_net_raw+p
/usr/sbin/clockdiff = cap_net_raw+p
如果想檢視某個程序的 capabilities,可以直接使用 getpcaps
,後面跟上程序的 PID:
$ getpcaps 1234
如果想檢視一組相互關聯的執行緒的 capabilities(比如 nginx),可以這麼來看:
$ getpcaps $(pgrep nginx)
這裡你會看到只有主執行緒才有 capabilities,子執行緒和其他 workers 都沒有 capabilities,這是因為只有 master 才需要特殊許可權,例如監聽網路埠,其他執行緒只需要響應請求就好了。
設定檔案的 capabilities 可以使用 setcap
,語法如下:
$ setcap CAP+set filename
例如,將 CAP_CHOWN
和 CAP_DAC_OVERRIDE
capabilities 新增到 permitted
和 effective
集合:
$ setcap CAP_CHOWN,CAP_DAC_OVERRIDE+ep file1
如果想移除某個檔案的 capabilities,可以使用 -r
引數:
$ setcap -r filename
2. libcap-ng
安裝也很簡單,以 CentOS 為例:
$ yum install libcap-ng-utils
用法
libcap-ng 使用 filecap
命令來管理檔案的 capabilities。有幾個需要注意的地方:
filecap 新增刪除或檢視 capabilities 時,capabilities 的名字不需要帶
CAP_
字首(例如,使用NET_ADMIN
代替CAP_NET_ADMIN
);filecap 不支援相對路徑,只支援絕對路徑;
filecap 不允許指定 capabilities 作用的集合,capabilities 只會被新增到
permitted
和effective
集合。
檢視檔案的 capabilities:
$ filecap /full/path/to/file
遞迴檢視某個目錄下所有檔案的 capabilities:
$ filecap /full/path/to/dir
例如:
$ filecap /usr/bin
file capabilities
/usr/bin/newgidmap setgid
/usr/bin/newuidmap setuid
注意: filecap 只會顯示“capabilities 被新增到
permitted
和effective
集合中”的檔案。所以這裡沒有顯示 ping 和 arping。
遞迴檢視整個系統所有檔案的 capabilities:
$ filecap /
# or
$ filecap -a
設定檔案的 capabilities 語法如下:
$ filecap /full/path/to/file cap_name
例如:
$ filecap /usr/bin/tac dac_override
移除某個檔案的 capabilities:
$ filecap /full/path/to/file none
3. 總結
本文通過兩種工具演示瞭如何對可執行檔案的 capabilities 進行管理,並以 docker 為例,展現了 no_new_privs
的強大之處。如果條件允許,推薦大家以後儘量用 capabilities 來替代完整的 root 許可權或者設定 SUID 標識。
4. 參考資料
- Added no-new-privileges Security Flag to Docker
- 關於 no new privs 翻譯稿
微信公眾號
掃一掃下面的二維碼關注微信公眾號,在公眾號中回覆◉加群◉即可加入我們的雲原生交流群,和孫巨集亮、張館長、陽明等大佬一起探討雲原生技術