Analyze AppArmor and Ubuntu’s Unprivileged Namespace Restriction bypass
Author : 堇姬 Naup
前言
最近剛好遇到 AppArmor 相關的東東,突然想到 pumpkin 寫過的這篇
https://u1f383.github.io/linux/2025/06/26/the-journey-of-bypassing-ubuntus-unprivileged-namespace-restriction.html
所以順便研究及復現了一下,稍微紀錄
Unprivileged User Namespace
該機制主要用於在一個 Unprivileged user 下,可以在一個受限的環境裡拿到管理權限,該權限實質上在 host 上仍是 Unprivileged
當可以建立 Unprivileged User Namespace,可以使使用者能摸到更多 kernel 的組件,也增加了攻擊面
AppArmor
通常我們會通過 chmod 來去針對檔案設定,甚麼 user 甚麼 group 可不可以讀取寫入等權限
而 AppArmor 則是直接限制一個程式可以做甚麼,例如可以讀甚麼檔案
更詳細來說,其允許系統管理員透過定義應用程式應被授予哪些資源的存取權限,並拒絕所有其他資源的存取權限來實現最小權限原則
一個程式沒有設定,則以無限制的 Unconfined 設定檔來執行
AppArmor 實作限制了建立 Unprivileged User Namespace 或是其他的建立,因此使得攻擊面變小
這幾篇主要是要說明,如何繞過 AppArmor 來建立 Unprivileged User Namespace
接下來來詳細介紹 AppArmor 用法
先安裝
1 | sudo apt install -y python3-apparmor=3.0.4-2ubuntu2 |
先準備一個 file
1 |
|
位於
1 | naup96321@naup96321-virtual-machine:~/Desktop/apparmor-test$ pwd |
接下來要寫 config
可以先通過 aa-easyprof <binary path> > /etc/apparmor.d/home.naup96321.Desktop.apparmor-test.apparmortest
產生基礎設定檔
這邊我們添加禁止訪問 /etc/passwd
1 | .apparmortest |
輸入這個生效
1 | sudo apparmor_parser -r /etc/apparmor.d/home.naup96321.Desktop.apparmor-test.apparmortest |
執行後就會發現不能開啟了
1 | naup96321@naup96321-virtual-machine:~/Desktop/apparmor-test$ ./apparmortest |
通過 aa-status 查看狀態
可以發現其已經在 enforce mode 了
enforce node : 違反 rules 會阻擋及記錄
complain mode : 違反 rules 會記錄
1 | naup96321@naup96321-virtual-machine:~/Desktop/apparmor-test$ sudo aa-status |
順帶一提 aa-disable 可以關閉
對 AppArmor 有初步了解後,先來架設環境
build env
先來裝一台 24.04 ubuntu
https://releases.ubuntu.com/24.04/
開一個 VMware 匯入
裝好後進去可以看到,預設的 kernel version 應該是 6.14.0-35-generic
接下來我們要替換到 vm 上的 kernel 成我們自己編譯的
Ubuntu 是基於原生的 linux kernel 來 patch 的
https://launchpad.net/ubuntu/+source/linux/6.11.0-18.18
在這網站有兩個檔案
一個是原來的 kernel,一個是 diff
這份 diff 6.11.0-18.18 前面的 6.11.0 是 kernel version,18.18 是 ubuntu 自己維護的版本
1 | wget https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/linux/6.11.0-18.18/linux_6.11.0.orig.tar.gz |
接下來拿 diff patch 後,我拿 host 上的 config 編譯
1 | cd linux-6.11.0 |
之後要將機器上的 kernel 替換,不過當 install 完後,要進入到 grub 將 kernel 替換,不過我一直抓不到 grub 時間,所以去修改 /etc/default/grub
1 | sudo make -j8 modules_install |
進到 grub 後,Ubuntu 高級選項 -> 選擇對應 kernel version
進去後就可以看到 kernel 版本已經被替換了
Analyze
Ubuntu 實作了一套基於 AppArmor 的東西,來讓一般使用者不能創建 unprivileged user namespace ,這個東西可以讓你在某個 namespace 中擁有 root,不過在全局上仍屬於一般使用者,但是通過 unprivileged user namespace ,可以摸到更多 kernel 的組件,以增加攻擊面
舉個例子,若在 host 上使用這個功能
1 | naup@naup-VMware-Virtual-Platform:~/Desktop/apparmor-test$ ip link set lo up |
會直接被 reject 掉
但如果設定一個 user namespace 中建立 network namespace,則可以正常執行
這篇主要是要講如何 bypass AppArmor 來重新可以建立 unprivileged user namespace
首先先嘗試在 VM 上建立 unprivileged user namespace
1 | naup@naup-VMware-Virtual-Platform:~/Desktop/apparmor-test$ unshare -r -n -m /bin/bash |
可以在看到被 reject 了,通過 dmesg 可以看到
第一條當啟動想要創建 namespace 時,他轉換 profile 成 unprivileged_userns
在轉換完成 profile 後,在新的受限制環境中,進程嘗試獲取 sys_admin 系統管理員權限,但被 AppArmor 拒絕
可以來看看 patch
1 | +struct aa_label *aa_profile_ns_perm(struct aa_profile *profile, |
這段 code 會切換 profile
檢查方式是若當前 profile 是 unconfined 且是預設的 unconfined,就會分配一個 unprivileged_userns 的 profile
1 | // security/apparmor/task.c |
來看看這個 profile
該 profile deny 了所有 capability,也就是說所有需要特殊 capability 的操作都會被拒絕,建立 namespace 的操作缺少了 sys_admin 的 capability,導致建立失敗
1 | // /etc/apparmor.d/unprivileged_userns |
從上述就可以知道,原本 unconfined 的 profile 被轉換成 unprivileged_userns 是無法建立 namespace 的關鍵,因此要來了解一下
第一個 unconfined 是從這個結構中的 mode flags 比對是否是 APPARMOR_UNCONFINED,只要是 unconfined 就行
這件事不太能動,因為切換 AppArmor 模式是需要 root 的,不過若是能切換模式成 enforce 或 complain 就可以 bypass 了
1 |
|
另外一個是檢查,這個 macro 會拿到自身 namespace,並拿到 namespace 的 unconfined 預設設定檔並比對當前 profile,如果一樣就會進到發 unprivileged_userns 分支
1 |
|
而 AppArmor 判斷當前使用的設定檔設定在 /proc/self/attr 下
1 | naup@naup-VMware-Virtual-Platform:~/Desktop/apparmor-test$ ls -la /proc/self/attr |
嘗試去 cat /proc/self/attr/current 可以看到他顯示當前使用的是 unconfined,這會顯示當前使用的設定檔是哪一個,並且有寫入權限,因此可以嘗試修改他,不過若直接寫入會失敗,通常需要修改這下面的東西,都需要通過一定格式來修改
這部分來看 code
首先是這部分用來建立 proc 下的東西,其中也包含 current,另外可以看到所有操作都是在 proc_pid_attr_operations 這張 vtable 上
1 | // fs/proc/base.c |
這張 vtable 紀錄了 write 相關的操作
1 | static const struct file_operations proc_pid_attr_operations = { |
前面會做一些檢查,之後呼叫正確 LSM (看起來像一個 hook,lsmid 選擇哪個 LSM 來處理)
LSM 自己解析 attribute 名稱與內容,並設定
https://lwn.net/Articles/940180/
1 | static ssize_t proc_pid_attr_write(struct file * file, const char __user * buf, |
從這裡可以找到 hook 的函數是 apparmor_setprocattr
1 | // /security/apparmor/lsm.c |
向下追可以看到,先找到要設定的屬性,若修改 current 就會轉換成對應 id
接下來 call do_attr 來設定該 attribute
1 | // /security/apparmor/lsm.c |
AppArmor 要處理:
1 | echo "<command> <args>" > /proc/self/attr/current |
這個 function 就是解析 command,然後呼叫對應的 aa_change_profile / aa_setprocattr_changehat 等 API
所以通過這個方式可以簡單的去操作當前使用的 unconfined profile 了,如通過 aa_change_profile 來去設定當前 profile flags,或是當前 profile 是哪個等等,完美~
1 | // security/apparmor/lsm.c |
exploit
首先其實蠻清楚應該要做甚麼的,他檢查了兩項,現在可以通過 /proc/self/attr/current 的 changeprofile 來去切換當前使用的 profile
那他檢查了當前是不是預設的 unconfined,因此只要找到機器上其他 uncondined 的 profile change 過去就好了,可以通過 aa-status 來看機器上有哪些,舉例來說 toybox 之類的 profile
1 | if (profile_unconfined(profile) && |
以下是完整 exploit
1 |
|
demo
After all
遇到最大的坑是我 VM 的 grub 進不去XD