WEB應(yīng)用安全設(shè)計(jì)思想
一、前言
我一直在思考的一個(gè)問題,就是安全問題的本質(zhì)到底是什么。我們見到過各種各樣的攻擊,也做過各種各樣的防御方案。有的方案好,有的方案卻有缺陷。那么好的方案好在哪里,為什么就能夠抵抗攻擊,到底什么特性使得攻擊者的成本升高了,使得風(fēng)險(xiǎn)降低了。這中間是否有什么共同的東西呢?
經(jīng)過一段時(shí)間的思考和觀察,我初步得出了一個(gè)結(jié)論:安全問題的本質(zhì)是信任問題。
二、信任關(guān)系的劃分是安全設(shè)計(jì)的基礎(chǔ)
安全問題的本質(zhì)是信任問題。
提到這個(gè),不得不說一個(gè)信任域的概念。當(dāng)系統(tǒng)信任某些單元時(shí),由這些單元組成的一片區(qū)域可以稱之為信任域。在數(shù)據(jù)流圖或者是拓跋圖上,都可以用一個(gè)邊界把這個(gè)域給界定出來。我說的這個(gè)概念,是一個(gè)廣義的概念,任何存在信任關(guān)系的系統(tǒng)中,都可以存在信任域。
比如一個(gè)機(jī)場(chǎng),人們要登機(jī),必須要先經(jīng)過安檢,那么過了安檢后,在候機(jī)廳候機(jī),就可以把候機(jī)廳看做是一個(gè)信任域。因?yàn)閷?duì)于機(jī)場(chǎng)來說,候機(jī)廳內(nèi)的區(qū)域是可信的。而候機(jī)廳外的區(qū)域是不可信的。
機(jī)場(chǎng)的安檢就是對(duì)跨越信任邊界的一個(gè)檢查。會(huì)檢查有沒有刀具,有沒有液體、打火機(jī)等。
那么安全問題是怎么發(fā)生的呢?首先是沒有合理的劃分信任域,或者是信任域比較混亂。
其次就是信任邊界的檢查出現(xiàn)問題的時(shí)候。這些問題可以是檢查不夠充分,或者是檢查沒有覆蓋到整個(gè)信任邊界。
而這些問題導(dǎo)致的結(jié)果,都是產(chǎn)生信任危機(jī),也就產(chǎn)生安全問題了。
對(duì)于傳統(tǒng)的內(nèi)存攻擊來說,一個(gè)字符串超出了分配給它的指定空間長度,也可以看做是對(duì)信任域的破壞,或者是缺乏審計(jì)。
所以信任域和信任邊界是非常重要的東西。在做安全方案的時(shí)候,首先就要依據(jù)資產(chǎn)等級(jí),去劃分信任域和信任邊界。
我們要知道我們到底要保護(hù)什么東西,然后去分析有什么途徑能夠達(dá)到這些要保護(hù)的信任域。
在圈子里經(jīng)常講的一個(gè)笑話就是,怎么做到安全?撥網(wǎng)線最安全。首先,這是一個(gè)謬論,因?yàn)榫W(wǎng)線拔掉后,可用性會(huì)受到影響。安全方案應(yīng)該盡可能的避免犧牲可用性為代價(jià),應(yīng)該是為業(yè)務(wù)和應(yīng)用服務(wù)的。拔網(wǎng)線是一種舍本逐末的做法。
其次,拔了網(wǎng)線真的就安全了嗎?
我們把物理隔絕的系統(tǒng)看做是一片信任域,那么它會(huì)信任什么?如何與外界做數(shù)據(jù)交互?
簡單的頭腦風(fēng)暴一下,就可以知道,這樣的系統(tǒng),可能會(huì)與外界發(fā)生數(shù)據(jù)交互的情況:
1. U盤有可能拷貝數(shù)據(jù)
2. 無線網(wǎng)卡有可能自動(dòng)連接
3. 可能有人為的手工操作
那么以上這三條,都是有可能穿越我們的信任邊界,產(chǎn)生數(shù)據(jù)流動(dòng)的行為。原本物理隔絕就是為了不信任外界的一切,產(chǎn)生數(shù)據(jù)流動(dòng)后,就可能破壞信任關(guān)系。
再回過頭來看上面的機(jī)場(chǎng)的案例,把客流量看做是數(shù)據(jù)流量,它將穿越一道信任邊界,進(jìn)入候機(jī)廳這個(gè)信任域,所以機(jī)場(chǎng)有安檢,來專門檢查這個(gè)穿越信任邊界的數(shù)據(jù)。安檢就是機(jī)場(chǎng)的安全方案。
-tips--------------------------------------------------------------------------
如果A信任B,或者A依賴于B,則B可以決定A的安全。常見的案例比如軟件中使用了第三方包,則第三方包可以決定A中相關(guān)數(shù)據(jù)的安全。
-------------------------------------------------------------------------------
某些視頻播放軟件使用了很多第三方的庫來解析很多不同的視頻格式,當(dāng)?shù)谌綆斐霈F(xiàn)安全問題時(shí),則直接導(dǎo)致這些視頻播放軟件也出現(xiàn)安全問題。
所以安全域的劃分是安全方案的基礎(chǔ),劃分了安全域后,才能比較有針對(duì)性的設(shè)計(jì)安全方案。
三、訪問控制是安全設(shè)計(jì)的核心
訪問控制不僅僅包括權(quán)限。權(quán)限僅僅只是訪問控制的一部分。這里我們通常所說的權(quán)限都是垂直權(quán)限控制,它一般是基于角色的(role based)。
比如一個(gè)論壇里面,有匿名用戶,他們可能看不了帖子的內(nèi)容。有普通用戶,他們能看帖子的內(nèi)容。有管理員,他們能刪帖子,能置頂帖子。
那么匿名用戶、普通用戶、管理員就是三個(gè)不同的角色。
我們的大部分訪問控制系統(tǒng),都是基于角色的。普通用戶沒辦法執(zhí)行管理員的操作,因?yàn)樵L問控制系統(tǒng)會(huì)校驗(yàn)用戶的角色,以決定他們是否有足夠的權(quán)限去執(zhí)行一次訪問。
訪問控制系統(tǒng)一般在整個(gè)系統(tǒng)中處于一個(gè)比較中心的位置,也只有讓他處在一個(gè)中心的、關(guān)鍵的位置,才能保證每次訪問都由它來控制。
但是目前我們的大多數(shù)系統(tǒng)都僅僅是垂直權(quán)限控制,而對(duì)水平權(quán)限控制方面卻做的不太好。
什么是水平權(quán)限控制?
這個(gè)概念是相對(duì)于垂直權(quán)限控制來說的。
A與B都是同一個(gè)角色的普通用戶。A上傳了一個(gè)頭像,系統(tǒng)給它編號(hào)為123,正常情況下,A可以執(zhí)行http://www.test.com/delete?id=123”去刪除自己的頭像。
但是由于這個(gè)刪除操作僅僅校驗(yàn)了用戶的角色,而沒有校驗(yàn)提交該請(qǐng)求的用戶是否是A,從而導(dǎo)致B可以提交以上請(qǐng)求,去刪除A的頭像。
這就是一個(gè)典型的水平權(quán)限控制出錯(cuò)的例子。
而很多系統(tǒng)中,同一個(gè)角色的用戶可以加入不同的用戶組,這些一個(gè)個(gè)的用戶組,就是一個(gè)水平權(quán)限控制的系統(tǒng)。
只是問題往往出在訪問控制系統(tǒng)的粒度上。如果劃分的粒度不夠細(xì),那么一個(gè)用戶組內(nèi)的用戶是否可以刪除或修改各自的數(shù)據(jù)?
對(duì)于粒度的劃分,我把一個(gè)訪問控制系統(tǒng)中的最小單位稱之為一個(gè)原子權(quán)限。無論是水平權(quán)限系統(tǒng)還是垂直權(quán)限系統(tǒng),可能都是對(duì)原子權(quán)限的不同組合。
這個(gè)問題實(shí)際上是一個(gè)非常難以解決的問題,特別是在已經(jīng)成型的大型系統(tǒng)中。對(duì)于現(xiàn)在的大型互聯(lián)網(wǎng)公司來說,網(wǎng)站的代碼一般都是幾十G的數(shù)量級(jí),業(yè)務(wù)系統(tǒng)繁多。而水平權(quán)限控制的一般要求是,將所操作的數(shù)據(jù)與用戶聯(lián)系起來。
回到上面的例子:delete?id=123
那么怎么知道123這條數(shù)據(jù),是A的呢?系統(tǒng)無從判斷,只能去查詢user表。如果業(yè)務(wù)系統(tǒng)一復(fù)雜,可能就涉及到跨表查詢或者是聯(lián)合查詢,甚至是跨庫查詢,這基本上是一場(chǎng)噩夢(mèng)。
可是如果不進(jìn)行二次查詢,則無法在根本的地方解決這個(gè)問題??墒嵌尾樵冇謺?huì)帶來性能上的消耗。真是一個(gè)很矛盾的事情。
所以最好的做法是在設(shè)計(jì)數(shù)據(jù)層的時(shí)候,事先考慮好這個(gè)問題,做好數(shù)據(jù)與用戶之間的關(guān)聯(lián)性。
如果已經(jīng)成型的系統(tǒng),就只能在外面包一層,把這個(gè)問題隱藏起來了。在本文的后面,會(huì)提到這種做法。
除了水平和垂直權(quán)限控制外,實(shí)際上一些規(guī)則,也可以看做是訪問控制。比如瀏覽器里的SOP(same original policy)。DOM、cookie等都有同源策略,也略有差別。但這些規(guī)則,都是屬于訪問控制系統(tǒng),在整個(gè)安全體系中,處于核心的位置。
-tips--------------------------------------------------------------------------
訪問控制系統(tǒng)一般會(huì)針對(duì)數(shù)據(jù)的RWX(讀、寫、執(zhí)行)屬性進(jìn)行授權(quán),對(duì)發(fā)起請(qǐng)求方則進(jìn)行水平或垂直的檢查。
-------------------------------------------------------------------------------
而在WEB中,極其理想的狀態(tài),可以大膽的想象為,以session為單位建立原子權(quán)限,將數(shù)據(jù)與session關(guān)聯(lián)起來后,每個(gè)不同的session就是不同的信任域,對(duì)每個(gè)跨越信任邊界的請(qǐng)求進(jìn)行水平、垂直的權(quán)限檢查,這樣就是一個(gè)極端理想的權(quán)限體系。
這只是一個(gè)理想模型,在實(shí)踐中,需要根據(jù)實(shí)際情況進(jìn)行分析。
四、數(shù)據(jù)與代碼分離的思想是安全設(shè)計(jì)的原則
最典型的體現(xiàn)數(shù)據(jù)與代碼分離思想的是模板系統(tǒng)。
比如velocity,在渲染html的時(shí)候,程序員可以寫vm模板,一些靜態(tài)寫死的內(nèi)容就是代碼,而通過變量,經(jīng)過渲染才最終展現(xiàn)的內(nèi)容則稱之為數(shù)據(jù)。一個(gè)典型的例子如下:
-code--------------------------------------------------------------------------
test
-------------------------------------------------------------------------------
代碼與數(shù)據(jù)如果沒有分離,就會(huì)導(dǎo)致代碼混亂,數(shù)據(jù)變成代碼的一部分去執(zhí)行。比較常見的例子就是PHP里的SQL寫法:
-code--------------------------------------------------------------------------
$sql = "SELECT * FROM article WHERE articleid='".$_GET[id]."'";
-------------------------------------------------------------------------------
如果參數(shù) id 中帶有單引號(hào),就會(huì)閉合掉代碼中的單引號(hào),從而導(dǎo)致數(shù)據(jù)變成代碼執(zhí)行。
所以這個(gè)注射的本質(zhì)問題還是沒有做好數(shù)據(jù)與代碼的分離。
比較好的做法是如下java代碼中的使用變量綁定,很好的做到了代碼與數(shù)據(jù)分離
-code--------------------------------------------------------------------------
String sql="Insert into table VALUES(?,?)";
List
但是并不是說使用了模板系統(tǒng)就一定分離了數(shù)據(jù)與代碼。
因?yàn)樵陬愃啤皉ender”或者是“transform”的過程中,往往存在一個(gè)將數(shù)據(jù)進(jìn)行規(guī)范化的過程。這個(gè)過程也可能出現(xiàn)問題,從而導(dǎo)致代碼可以混淆數(shù)據(jù)進(jìn)行執(zhí)行。
比較好的做法是,數(shù)據(jù)中不能包含有在代碼中存在語義的字符。
參考如下例子:
-code--------------------------------------------------------------------------
Set-Cookie: name=id\r\nP3P: xxxxxxxxxxxxxx\r\n
-------------------------------------------------------------------------------
紅字部分是用戶的輸入。
在HTTP的標(biāo)準(zhǔn)中,冒號(hào)“:”,等號(hào)“=”,換行符CRLF“\r\n”,百分號(hào)“%”等字符都是有具體的語義的,屬于代碼部分。所以正常的用戶數(shù)據(jù)中不應(yīng)該包含有這些字符。
如果出于需求一定要包含怎么辦?按照標(biāo)準(zhǔn)將這些字符全部encode。
在HTTP標(biāo)準(zhǔn)中可以使用urlencode,比如等號(hào)就變成了“%3d”。
這樣就做到了代碼與數(shù)據(jù)的分離。
代碼與數(shù)據(jù)分離原則的本質(zhì)還是體現(xiàn)了安全問題是信任問題這一思想。
代碼是否應(yīng)該信任數(shù)據(jù),或者說代碼應(yīng)該信任怎樣的數(shù)據(jù),是這個(gè)原則的本質(zhì)。
在應(yīng)用中,比較好的例子是json、XSLT,這些方法都比較好的做到了數(shù)據(jù)與代碼分離,所以在開發(fā)中多使用這些比較好的方法,無形中就提高了安全性。
五、最佳實(shí)踐一:Secure By Default
經(jīng)??梢钥吹揭恍?quán)威文檔上推薦使用“default denied”,這就是“Secure By Default”
的一種體現(xiàn)。
“Secure By Default”可以說是一個(gè)最佳實(shí)踐。在很多時(shí)候,這個(gè)思想應(yīng)該上升到戰(zhàn)略的高度。只有真正做到“Secure By Default”,才能保證網(wǎng)站的安全。
因?yàn)殡S著時(shí)間的推移和系統(tǒng)的發(fā)展、膨脹,會(huì)變得越來越臃腫。一個(gè)大系統(tǒng)發(fā)展到后期,基本上沒有一個(gè)人能了解系統(tǒng)的全部,而變化卻每天都在發(fā)生。所以,在這種情況下,只有使用“Secure By Default”的思想來制定安全方案。
白名單往往是實(shí)現(xiàn)“Secure By Default”的方法。與黑名單不同,白名單的思想很好的體現(xiàn)了“default denied”。下面以XSS的防御問題舉例。
對(duì)于一些HTML的標(biāo)簽和事件,黑名單的做法是列出危險(xiǎn)的標(biāo)簽和事件,然后禁止他們。比如列出