注* 做者發表這篇文章的時間較早,某些方法可能並非最好的解決方案,但針對這種漏洞進行的攻擊還依然可見,如早期的:QQMail郵件泄露漏洞,下面介紹的是對這種攻擊原理的介紹。javascript
不久以前,我寫了一篇文章《一個微妙的JSON漏洞》,文中講到這個漏洞可能會致使敏感信息泄露。針對該漏洞的特色,經過覆蓋JavaScript數組構造函數以竊取(暴露)JSON返回數組,而如今大多數瀏覽器還沒法防範這種攻擊。html
然而,經過和微軟的Scott Hanselman交流,我瞭解到另一個方法可能會影響更多的瀏覽器。在上週的挪威開發者大會上,我作了一個針對Json劫持漏洞的演示。java
在我進一步講以前,我先說一說,這個漏洞可能帶來的影響。ajax
在如下條件下,會出現這個漏洞:首先暴露JSON服務,而且該服務會返回敏感數據;返回JSON數組;對GET請求作出響應;發送這個請求的瀏覽器啓用了JavaScript而且支持_defineSetter_方法。json
若是咱們不使用JSON發送敏感數據,或者只對報文請求作出響應,那麼咱們的網站就不存在這個漏洞。數組
我不喜歡用流程圖展現這個過程,我會盡可能用圖表描述。在第一頁截圖上,咱們能夠看到不知情的受害者登錄漏洞網站,漏洞網站返回了一個身份認證的cookie。瀏覽器
咱們均可能收到過一些垃圾郵件,郵件中附有連接,發送者聲稱這有一段搞笑視頻。這些大量的垃圾郵件都是一些別有用心的人發的。緩存
可是實際上,這些連接指向的是那些壞傢伙本身網站。當咱們點擊了連接,接下來的兩個步驟會迅速進行。第一,咱們的瀏覽器向這些網站發送請求。安全
第二,那些網站會響應一些包含JavaScript的HTML。這些JavaScript會帶一個script標記。當瀏覽器檢測到script標記,它就會向那些漏洞網站再發一個下載腳本的GET請求,攜帶着身份驗證的cookie。服務器
這樣,那些壞傢伙就假裝成了受害者的瀏覽器,利用其身份發出了一個包含敏感數據的JSON請求。接着,把JSON加載爲可執行的JavaScript,這樣以來,那些黑客就可以獲取到這些數據。
爲了加深理解,咱們能夠看看一個攻擊的實際代碼。假如漏洞網站返回帶有敏感數據的JSON響應經過以下方式發送:
[Authorize] publicJsonResultAdminBalances(){ varbalances=new[]{ new{Id=1,Balance=3.14}, new{Id=2,Balance=2.72}, new{Id=3,Balance=1.62} }; returnJson(balances); }
須要說明的是,上面的演示不是專門針對ASP.NET或者ASP.NET MVC,我僅僅是恰巧用ASP.NET MVC來演示這個漏洞而已。
假如這是HomeController的一種方法,咱們經過對/Home/AdminBalances發送了一個GET請求,而且返回以下JSON文本:
[{「Id」:1,」Balance」:3.14},{「Id」:2,」Balance」:2.72},{「Id」:3,」Balance」:1.62}]
注意,我定義這個方法時使用了Authorize屬性,用來驗證請求者的身份。因此一個匿名的GET請求將不會獲得敏感數據。
重要的是:這是一個JSON數組。包含JSON數組的文本是一個有效的JavaScript腳本,而且能夠被執行。僅僅包含JSON對象的腳本不是一個有效的JavaScript可執行文件。
舉個例子,若是咱們有一個包含以下JSON代碼的JavaScript文檔:
{「Id」:1,」Balance」:3.14}
而且有一個指向這個文檔的腳本標籤:
<script src=」http://example.com/SomeJson」></script>
這樣,咱們會在HTML頁中獲得一個JavaScript錯誤。然而,假若存在一個不幸的巧合,若是咱們有一個script標籤,這個標籤指向僅僅含有一個JSON數組的文檔,這樣的話,這個標籤就會被誤認爲是有效的JavaScript,而且數組會生效。
下面就讓咱們看看那些別有用心的人的服務器上的HTML頁。
注*這裏咱們能夠看到使用 Json Object 而不是Json Array返回你的數據,能夠在必定程度上預防這種漏洞。
<html> ... <body> <scripttype="text/javascript"> Object.prototype.__defineSetter__('Id',function(obj){alert(obj);}); </script> <scriptsrc="http://example.com/Home/AdminBalances"></script> </body> </html>
看到了什麼?黑客正在改變對象的原型,用_defineSetter_這種特殊方法,覆蓋JSON對象(Object)本來應有的默認行爲。
在這個例子中,一個命名合適的ID在任什麼時候候均可以被設置到任何對象上時,一個匿名的函數將會被調用,這個函數將會利用alert 函數顯示屬性值。注意,這時腳本僅僅會將數據發回給那些壞傢伙,而不會發送敏感數據。
就像以前提到的,壞傢伙須要使咱們在登陸漏洞網站後不久而且在會話仍舊有效時,訪問他的惡意的網頁。經過含有惡意網站連接的郵件進行釣魚攻擊的方式是很典型的。
若是你仍舊點擊連接登陸原網站,瀏覽器將會在加載腳本標籤中引用的腳本並向網站發送你的身份驗證cookie。直到鏈接上原網站,咱們對JSON數據發出一 個有效的身份驗證請求,而且會收到在咱們瀏覽器中響應的有效數據。這些話可能聽着很熟悉,由於它是一個真的變種僞造跨站請求,以前我寫過這種狀況。
由於在IE8上_defineSetter_是一個無效的方法,因此在IE8上看不到現象。我在Chrome和Firefox上都試過,均可以。
避免這個漏洞也很簡單:或者從不發送JSON數組,或者只訪問HTTP POST以得到須要的數據。舉個例子:在ASP.NET MVC中,你能夠用AcceptVerbsAttribute來實現:
[Authorize] [AcceptVerbs(HttpVerbs.Post)] publicJsonResultAdminBalances(){ varbalances=new[]{ new{Id=1,Balance=3.14}, new{Id=2,Balance=2.72}, new{Id=3,Balance=1.62} }; returnJson(balances); }
這個方法的一個問題就是:像jQuery這樣的不少JS庫默認都是用GET方式發送JSON請求,而不是POST。舉個例子,$.getJSON 默認發起的是GET請求。因此,當進行這種JSON訪問時,咱們須要確信咱們是用客戶端庫發起的POST請求。
ASP.NET和WCF JSON服務端實際上在對象中用了「d」屬性,包裹了他們的JSON,這我在另外一篇文章中討論過:
必須通過這些屬性得到數據,彷佛有些奇怪,但這須要經過一個客戶端代理來實現來去除「d」屬性,,以便不影響最終用戶。
在ASP.NET MVC下,絕大多數開發者沒有生成客戶端代理,而是使用jQuery和其餘相似的庫,這樣一來使用「d」屬性的就有些尷尬。
注*其實MVC的方法有點複雜,到這裏咱們能夠看出,JSON劫持漏洞是要以在受豁者的瀏覽器上執行JSON返回對象爲前提的,其實Google使用了一種更加聰明的方法,經過添加「死循環」命令,防止黑客運行這段腳本,可參見這篇文章:爲何谷歌的JSON響應以while(1);開頭?
檢查首部(Http-Header)怎麼樣?
一些人可能會有疑問:「爲何不在響應一個GET請求以前,用一個特殊的首部對JSON服務進行檢查?就像X-Requested- With:XMLHttpRequest或者Content-Type:application/json」。我認爲這多是個過渡,由於大多數的客戶端 庫會發送一種或兩種Header,可是瀏覽器響應腳本標籤的GET請求不這樣。
問題是:在過去某個時候,用戶可能會發出合法的JSON GET請求,在這種狀況下,這個漏洞可能會隱藏在用戶瀏覽器的正常請求之間。也是在此種狀況下,當瀏覽器發出GET請求,這種請求可能會緩存在瀏覽器和代 理服務器的緩衝區。咱們能夠嘗試着設置No-Cache header,這樣,咱們信任瀏覽器和全部的代理服務器可以正確地實現高速緩存而且信任用戶也不會被意外地覆蓋。
固然,若是咱們用SSL提供JSON文本,這個特定的緩存問題將會很容易被解決。
注*這裏咱們能夠看到防範方法這三:不要cache你的ajax請求,不過目前彷佛全部的js庫默認都是不cache的。
真正的問題在哪裏?
Mozilla Developer Center發表的一篇文章中寫道:對象和數組初始化設定項在賦值時不該該調用setters方法。這一點我贊成。儘管有評論認爲:也許瀏覽器真的不該該執行腳本。
可是在一天結束的時候,分派責任並不能使你的網站更加安全。這些瀏覽器的怪癖毛病將會時不時地出現。咱們做爲網站的開發者須要解決這些問題。 Chrome2.0.172.31和Firefox3.0.11也有這個軟肋。IE8沒有這個問題,由於它不支持這種方法,我也沒有在IE7或者IE6中 試驗過。
在我看來,在當前的客戶端庫下,安全訪問JSON的默認方式應該是POST,而且咱們應該選擇GET,而不是其餘方式。您以爲呢?您所瞭解的其餘平臺是怎麼解決這個問題的呢?我很想聽聽你們的想法。
原文 haacked.com