天天看點

Docker 容器逃逸案例分析

本文參考自《docker 容器與容器雲》

這個容器逃逸的 case 存在于 docker 1.0 之前的絕大多數版本。

目前使用 docker 1.0 之前版本的環境幾乎不存在了,這篇分析的主要目的是為了加深系統安全方面的學習。

嘗試用較為簡單的話來說明 linux 中 <code>capability</code> 的概念。

為了解決在某些場景下,普通使用者需要部分 <code>root</code> 權限來完成工作的問題。linux 支援将部分 <code>root</code> 的特權操作權限細分成具體的 <code>capability</code>,如果将某個 <code>capability</code> 配置設定給某一個可執行檔案或者是程序,即使不是 <code>root</code> 使用者,也可以執行該 <code>capability</code> 對應的特權操作。

以 unix v6 為基礎進行說明,目前主流的 linux 版本檔案系統的實作原理與 unix v6 差别不大。

unix 系統中與某一個程序密切相關的有兩個結構體,它們是 <code>proc</code> 結構體和 <code>user</code> 結構體。

<code>proc</code> 結構體中儲存了程序狀态、執行優先級等經常需要被核心通路的資訊,是以由 <code>proc</code> 結構體構成的資料 <code>proc[]</code> 是常駐記憶體的。

<code>user</code> 結構體中儲存了程序打開的檔案等資訊,由于核心隻需要使用目前執行程序的 <code>user</code> 結構體,是以當某一個程序被移至交換空間時, <code>user</code> 結構體也相應地會被移出記憶體。

<code>proc</code> 結構體中的 <code>p_addr</code> 指向的資料段,其起始部分的内容即為 <code>user</code> 結構體。

由于 <code>user</code> 結構體内容較多就不列出了,其中一個與檔案描述符相關的屬性是 <code>u_ofile[]</code>,會在後面提到。

檔案描述符是核心為了管理已被打開的檔案所建立的索引,是一個非負的整數。

檔案描述符儲存在程序對應 <code>user</code> 結構體的 <code>u_ofile[]</code> 字段中。

通過檔案描述符對檔案進行操作涉及到三個關鍵的資料結構,原理如下圖所示:

Docker 容器逃逸案例分析

說明1:

當一個程序啟動時,檔案描述符 <code>0</code> 表示 <code>stdin</code>,<code>1</code> 表示 <code>stdout</code>,<code>2</code> 表示 <code>stderr</code>,若程序再打開其它檔案,那麼這個檔案的檔案描述符會是 <code>3</code>,依次遞增。

說明2:

當兩個程序打開了同一個檔案時(即為圖中所示情況),對應到 <code>file[]</code> 中是兩個不同的 <code>file</code> 結構體,是以各自擁有獨立的檔案偏移量,不過指向的是同一個 <code>inode</code> 節點,是以修改的是同一個檔案。

說明3:

存在以下幾種情況(未必是所有情況,也許存在沒有列出的其它情況)會導緻兩個程序的檔案描述符指向同一個 <code>file</code> 結構:

父程序 fork 出了子程序。此時父程序與子程序各自的每一個打開檔案描述符共享同一個 <code>file</code> 結構

使用 <code>dup</code> 或是 <code>dup2</code> 函數來複制現有的檔案描述符

我們知道 docker 容器的 namespace 隔離是 docker daemon 程序通過調用 <code>clone()</code> 函數,并控制 <code>clone</code> 函數中的 flag 參數來實作的。我們查閱文檔可以發現這一句描述

if clone_files is set, the calling process and the child process share the same file descriptor table.

說明 docker daemon 程序與容器程序共享了檔案描述符。

函數原型:

<code>int open_by_handle_at(int mount_fd, struct file_handle *handle, int flags);</code>

函數功能:

引自 linux 手冊

the open_by_handle_at() system call opens the file referred to by handle. the mount_fd argument is a file descriptor for any object (file, directory, etc.) in the mounted filesystem with respect to which handle should be interpreted. the caller must have the cap_dac_read_search capability to invoke open_by_handle_at(). 譯: open_by_handle_at() 用于打開 <code>file_handle</code> 結構體指針所描述的某一個檔案 mount_fd 參數為 <code>file_handle</code> 結構體指針所描述檔案所在的檔案系統中,任何一個檔案或者是目錄的檔案描述符

linux 手冊中特别提到調用 <code>open_by_handle_at</code> 函數需要具備 <code>cap_dac_read_search</code> 能力

docker 1.0 版本對 <code>capability</code> 使用黑名單管理政策,并且沒有限制 <code>cap_dac_read_search</code> 能力,因而造成了這個容器逃逸 case

<code>file_handle</code> 結構體說明:

前面兩個字段都好了解,關鍵是 <code>f_handle[0]</code> 字段,它一般都會是一個 8 位元組的字元串,并且前 4 個位元組為該檔案的 inodenumber

另外 cve-2014-3519 這個漏洞也與 <code>open_by_handle_at()</code> 函數相關,有時間我再去研究一下那個 case

分析 <code>shocker.c</code> 所需要的儲備知識已經介紹完了。

我在代碼中用中文給出了比較詳細的說明,下面來看下這段容器逃逸 poc 代碼。

繼續閱讀