1. 程式人生 > >Linux--Sandbox

Linux--Sandbox

在電腦保安領域,沙箱(Sandbox)是一種程式的隔離執行機制,其目的是限制不可信程序或不可信程式碼執行時的訪問許可權。沙箱技術經常被用於執行未經測試的或不可信的客戶程式。為了阻止不可信程式可能破壞系統程式或破壞其它使用者程式的執行,沙箱技術通過為不可信客戶程式提供虛擬化的記憶體、檔案系統、網路等資源,而這種虛擬化手段對客戶程式來說是透明的。由於沙箱裡的資源被虛擬化(或被間接化),所以沙箱裡的不可信程式的惡意行為可以被限制在沙箱中,或者在沙箱裡只允許執行在白名單裡規定的有限的API操作。

沙箱技術一直是系統安全領域的挑戰,不存在說哪一種方案是足夠安全的。沙箱技術方案通常是需要結合多種系統安全技術來實現,採用防禦縱深(Defence in Depth)的設計原則,築建多道防禦屏障,儘可能地將安全風險將為最低。比如,Google App Engine系統就是一個較好的實現,使用者編寫的java code會在Java沙箱中執行。但除了Java Sandbox之外,底層還有Linux沙箱。也就是說,即使使用者(也許是一個比較資深的黑客)已經掌握了Java沙箱的0-day漏洞,攻破了Java沙箱,但也不會給系統造成影響,因為底層還有Linux沙箱。除非該使用者同時還掌握了Linux的0-day漏洞,能以很快的速度突破Linux沙箱,使自己從一個普通uid提權到root,否則我們不能說攻擊成功。

Hypervisor技術天然地就可以用於構建虛擬機器沙箱,比如基於Xen或KVM的虛擬機器沙箱已經開始在某些雲端計算平臺中使用起來。但考慮到虛擬機器沙箱的效能開銷,它並不能滿足對效能敏感的應用場景。因此,這裡我們主要討論如何利用Linux kernel自身所提供的安全功能來構建有效的輕量級沙箱技術。(關於重量級的虛擬機器沙箱,我以後再另作介紹)

在討論之前,我們簡單羅列一下Linux安全模型相關的內容(假設讀者對此已經非常熟悉了):
(1) 每個程序都有自己的地址空間;
(2) MMU硬體機制來保證地址空間的隔離;
(3) Kernel是系統的TCB(Trusted Computing Base),是安全策略的制定者和執行者;
(4) 程序是最小的許可權邊界;
(5) root具有最高許可權,它能控制一切;
(6) 其它使用者受DAC(Discretionary Access Control)限制,如檔案系統的UGO許可權控制。

Linux Kernel還提供了與“程序降權(drop privilege)與訪問限制”有關的一些功能:
(1) setuid
(2) posix.1e capability
(3) chroot jail
(4) quota control (eg, cgroup, namespace)
(5) Linux Container
(6) Linux Security Module (LSM)
 

接下來我將介紹在實踐中如何利用這些安全構件來打造一個有效的Linux sandbox.

 

Setuid Sandbox主要基於Linux Kernel所提供的安全機制來實現。簡單地說,就是利用 random uid/gid + chroot() + capability 來達到限制不可信程序的訪問許可權。Setuid Sandbox的設計主要考慮以下幾方面:

1. Setuid

Linux中每個程序都會有一個uid,uid=0則為root使用者程序(privileged),uid>0則為普通使用者程序(unprivileged)。不同uid程序之間(不包括root程序)是相互隔離的,各自都有自己獨立的許可權,互不干擾。而root程序具有特權,它能幹任何事情。Linux uid/gid機制主要是用於程序的許可權隔離。如果你打算執行不可信的程式,那麼你可以在啟動該程式時為其分配一個random uid。一個可能的執行流程如下:

fork() -> setuid() -> {設定相關的程序資源限制, eg, RLIMIT_NPROC (0,0)} -> execve()

注意,setuid()只能由root許可權(或擁有 CAP_SETUID capability的普通使用者許可權)才能成功呼叫,所以這個執行流程需要藉助某個擁有root許可權的helper程式。比如,將helper程式設定為setuid root。

2. Chroot

Chroot是Linux kernel提供的另一個安全功能,它用於修改程序的根目錄。比如執行chroot("/tmp/sandbox/1/")則可以設定當前程序的根目錄為"/tmp/sandbox/1/",那麼該程序的檔案操作將被限制在"/tmp/sandbox/1/"中。注意,chroot()只能由root許可權(或擁有CAP_SYS_CHROOT capability的普通使用者許可權)才能成功呼叫。也許你馬上會想到按如下方式修改上面的執行流程:

fork() ->chroot() -> setuid() -> {...} -> execve(),

但注意這樣做是行不通的,因為在chroot()之後,execve()本要執行的binary檔案已經不可用了(程序的根目錄已經被重定位了)。一個解決此問題的簡單方法如下:

(1) helper建立一個子程序H,注意要用clone()和CLONE_FS,使得helper和H可以共享根目錄、當前目錄、等等;
(2) helper降權後執行execve("worker");
(3) worker(原helper程序)請求H去執行chroot();
(4) H執行chroot(),新的根目錄會對H和worker同時生效。
(5) H退出。

這個方法工作的前提是Helper需要設定RLIMIT_NOFILE為(0,0),並且對於不可信的Worker程序來說,在執行第4步之前應是可控的。

此外,對於Helper程式來說,由於它是以root身份執行,那麼就可能會成為攻擊點,即存在所謂的 "Confused Deputy Problem"。這個問題是說,因為Helper程序是root身份執行,它能幹所有特權操作;而實際我們在設計上是希望它只能做兩件事,即setuid和chroot,不希望它有更多的特權。一個程序的特權太多了,就會成為壞人攻擊的焦點,甚至有時是在無知的情況下,不經意地就幫壞人幹了壞事。為了解決這個問題,我們就可以在設計上引入Linux Capability機制。

3. Linux Capability

Linux支援Capability的主要目的是細化root的特權,使一個程序能夠以“最小許可權原則”去執行任務。比如拿ping程式來說,它需要使用原始套接字(raw_sockets),如果沒有Capability,那麼它就需要使用root特權才能執行;如果有了Capability機制,由於該程式只需要一個CAP_NET_RAW的Capability即可執行,那麼根據最小許可權原則,該程式執行時可以丟棄所有多餘的Capability,以防止被誤用或被攻擊。所以,Capability機制可以將root特權進行很好的細分,當前kernel(2.6.18)已支援30多種不同的Capability。注意在之前的kernel實現中,Capability只能由root程序持有,非root程序是不能保持任何Capability的。但是在2.6.24及以上的kernel版本中一個普通使用者程序也將可以持有capability。

小結

Setuid Sandbox實現起來比較簡單,無需修改Kernel,對線上的生產系統來說沒有什麼負面影響,在一定程度上可以用於隔離不可信程式碼的執行,目前它已被用作Google Chromium系統的隔離屏障之一。由於它完全依賴於kernel所提供的安全機制,除非攻擊者能找到kernel的0-day漏洞並通過攻擊獲得root許可權,否則setuid sandbox所提供的安全隔離是可以保證的。