JSON劫持漏洞攻防原理及演練

ourjs發表於2014-01-11

  注* 作者發表這篇文章的時間較早,某些方法可能並不是最好的解決方案,但針對這種漏洞進行的攻擊還依然可見,如早期的:QQMail郵件洩露漏洞,下面介紹的是對這種攻擊原理的介紹。

  不久之前,我寫了一篇文章《一個微妙的JSON漏洞》,文中講到這個漏洞可能會導致敏感資訊洩露。針對該漏洞的特點,通過覆蓋JavaScript陣列建構函式以竊取(暴露)JSON返回陣列,而現在大多數瀏覽器還無法防範這種攻擊。

  然而,通過和微軟的Scott Hanselman交流,我瞭解到另外一個方法可能會影響更多的瀏覽器。在上週的挪威開發者大會上,我做了一個針對Json劫持漏洞的演示。

  在我進一步講之前,我先說一說,這個漏洞可能帶來的影響。

  在以下條件下,會出現這個漏洞:首先暴露JSON服務,並且該服務會返回敏感資料;返回JSON陣列;對GET請求做出響應;傳送這個請求的瀏覽器啟用了JavaScript並且支援_defineSetter_方法。

  如果我們不使用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

相關文章