Seccomp 是一個 Linux Kernel 提供的安全機制,用意在於限制一個 Process 可以調用的系統呼叫(system call)。
system call 依照 wiki 百科的定義如下,系統呼叫(英語:system call),指運行在使用者空間的程式向作業系統核心請求需要更高權限運行的服務,系統調用提供使用者程式與作業系統之間的介面。
那先來看一下 Seccomp 運作的效果。其實 Docker 本身預設就有套用 Seccomp 的機制,只是通常被使用者所忽略,這邊來試著觀察看看。
docker run -it --rm aeifkz/my-ubuntu:v1.0 bash ;
wget https://github.com/genuinetools/amicontained/releases/download/v0.4.9/amicontained-linux-amd64 && chmod +x amicontained-linux-amd64 ;
./amicontained-linux-amd64 ;
# 顯示沒權限執行
unshare ;
會發現有 55 個 system call 被阻擋了,其中也包含了 unshare,至於 Docker 預設阻擋 System call 清單可參考 Significant syscalls blocked by the default profile、default.json。相關結果顯示如下 :
Container Runtime: docker
Has Namespaces:
pid: true
user: false
AppArmor Profile: docker-default (enforce)
Capabilities:
BOUNDING -> chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap
Seccomp: filtering
Blocked Syscalls (55):
MSGRCV SYSLOG SETSID USELIB USTAT SYSFS VHANGUP PIVOT_ROOT _SYSCTL ACCT SETTIMEOFDAY MOUNT UMOUNT2 SWAPON SWAPOFF REBOOT SETHOSTNAME SETDOMAINNAME IOPL IOPERM CREATE_MODULE INIT_MODULE DELETE_MODULE GET_KERNEL_SYMS QUERY_MODULE QUOTACTL NFSSERVCTL GETPMSG PUTPMSG AFS_SYSCALL TUXCALL SECURITY LOOKUP_DCOOKIE CLOCK_SETTIME VSERVER MBIND SET_MEMPOLICY GET_MEMPOLICY KEXEC_LOAD ADD_KEY REQUEST_KEY KEYCTL MIGRATE_PAGES UNSHARE MOVE_PAGES PERF_EVENT_OPEN FANOTIFY_INIT NAME_TO_HANDLE_AT OPEN_BY_HANDLE_AT SETNS KCMP FINIT_MODULE KEXEC_FILE_LOAD BPF USERFAULTFD
Looking for Docker.sock
在了解 Seccomp 可以阻擋呼叫特定的 system call 之後,想必只使用預設阻擋應該滿足不了各位的胃口了。但現實是骨感的,接下來我就開始澆熄各位的熱情(?)。
Seccomp 的設定檔案必須要在設定檔案中設定 defaultAction,其餘的話就可以例外去做開放設定。Action 的部分可以設定 SCMP_ACT_ALLOW、SCMP_ACT_ERRNO、SCMP_ACT_LOG 。基本的框架如下 :
seccomp.json 設定檔
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
]
}
docker run --rm -it --security-opt seccomp=seccomp.json aeifkz/my-ubuntu:v1.0 bash ;
# 可以執行不會被擋掉
unshare ;
那接下來就是要知道一支程式有用到那些 system call,先來講最不好用也最不準確的方法(?)。就是先將 Seccomp 切換到 LOG 模式,在呼叫指令去看 LOG 內記錄到的結果。聽起來很美好,但實際上不太可行。實作如下 :
seccomp.json
{
"defaultAction": "SCMP_ACT_LOG",
"syscalls": [
]
}
docker run --rm -it --security-opt seccomp=seccomp.json aeifkz/my-ubuntu:v1.0 bash ;
# 在宿主機追蹤輸出的 LOG 資訊
tail -f /var/log/syslog | grep docker | grep audit ;
# 這邊來測試 touch 這個指令
touch aaa ;
觀察 LOG 資訊會發現不適合人去作閱讀,其中包含了 arch=c000003e、syscall=270 的字樣,所以這個要怎麼解讀?
參考 Where do you find the syscall table for Linux?,觀察 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 的內容顯示 270 的系統呼叫是 __NR_pselect6。 但整個 touch 指令看下來只用到 0,1,270 這三個 system call 不太合理,而且這個代號也很難推到實際的設定檔上,所以我並不推薦使用這個方法。
所以如果想看到一支程式究竟使用了那些 system call,比較推薦的方式是使用 strace 指令去觀察,這邊沒有做太深入的研究,只需使用 -c (summary only) 參數列出程式用到的 system call 列表即可。
docker run -it --rm aeifkz/my-ubuntu:v1.0 bash ;
apt update && apt install -y strace ;
strace -c touch aaa ;
這邊會輸出結果如下 :
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00 0.000000 0 1 read
0.00 0.000000 0 7 close
0.00 0.000000 0 3 fstat
0.00 0.000000 0 9 mmap
0.00 0.000000 0 3 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 3 brk
0.00 0.000000 0 6 pread64
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 dup2
0.00 0.000000 0 1 execve
0.00 0.000000 0 2 1 arch_prctl
0.00 0.000000 0 4 openat
0.00 0.000000 0 1 utimensat
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 43 2 total
針對用白名單方式設定 Seccomp 會遇到一個問題就是怎樣測試完整個程式的流程,這個就會吃測試程式的功力,一旦少測試到某個流程可能就漏掉設定某個 system call,造成程式執行的異常。第二個問題更關鍵,不管是用 LOG 還是 strace 的方法似乎都會漏觀察到該程式呼叫的 system call。
假設今天 chmod 對我們而言是個萬惡的指令,我們不希望容器內可以呼叫 chmod,這時候會想要怎麼設定? 步驟如下 :
touch aaa ;
strace -c chmod +x aaa ;
這邊會顯示結果如下,但只能用猜名稱的方式去決定哪個可能是關鍵的 system call 呼叫,這邊猜測是 fchmodat。所以這種只能透過猜測的方式不太實用,更別提說要考慮到是否該 system call 會不會被其他程式或是功能所使用。
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
31.05 0.000059 7 8 mmap
17.37 0.000033 33 1 munmap
12.11 0.000023 7 3 mprotect
7.37 0.000014 3 4 close
6.32 0.000012 12 1 fchmodat
5.79 0.000011 3 3 brk
3.68 0.000007 2 3 newfstatat
3.16 0.000006 6 1 getrandom
2.63 0.000005 5 1 set_tid_address
2.11 0.000004 4 1 umask
2.11 0.000004 2 2 1 arch_prctl
2.11 0.000004 4 1 set_robust_list
2.11 0.000004 4 1 prlimit64
2.11 0.000004 4 1 rseq
0.00 0.000000 0 1 read
0.00 0.000000 0 4 pread64
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 2 openat
------ ----------- ----------- --------- --------- ----------------
seccomp.json
{
"defaultAction": "SCMP_ACT_LOG",
"syscalls": [
{
"name": "fchmodat",
"action": "SCMP_ACT_ERRNO",
"args": []
}
]
}
docker run --rm -it --security-opt seccomp=seccomp.json aeifkz/my-ubuntu:v1.0 bash ;
touch aaa ;
# 測試 chmod 改變屬性,顯示 Operation not permitted
chmod +x aaa ;
這就是 Seccomp 的基本功能,透過它限制容器內部可以呼叫的 system call,進而達到防護的效果。但是它實質上提供了甚麼防護呢? 這邊 先列舉攻擊篇的手法來進行驗證。
攻擊手法 | Seccomp 預設能否阻擋? |
---|---|
privileged + host pid | N |
--cap-add=ALL + host pid | N (作業8) |
privileged | N |
(CVE-2022-0492) unshare + cgroup 特權逃逸手法 | Y |
安裝 linux_module | N |
docker.sock 掛載 | N |
所以針對 seccomp 的使用上我個人比較建議採用預設值的黑名單即可。那如果想關閉 seccomp 的方式可使用 --privileged 參數,但這個方式非常糟糕,除了會一併關閉 Apparmor 機制之外,還會給予 root 所有的 CAP 能力,因此相當的不推薦。或者是用 --security-opt seccomp=unconfined 就不會啟動 seccomp 的防禦機制。
相關參考資料 : Basics of Seccomp for Docker、Lab: Seccomp、云原生安全 — seccomp应用最佳实践、Seccomp in Kubernetes、Seccomp 的相關調校方式、Allow/Disallow Syscalls via Seccomp。
今日總結 :