From: MBSD Technical Whitepaper
PS: MBSD是一家日本安全公司,最近好像經常分享技術文檔的樣子。
Cross Site Script Inclusion (XSSI) 跨站腳本包含是一種攻擊技術允許攻擊者通過惡意js繞過邊界竊取信息。具體的說,應該是通過潛入script標簽加載外部數據,for example:
#!javascript
<!-- attacker's page loads external data with SCRIPT tag -->
<SCRIPT src="http://target.wooyun.org/secret"></SCRIPT>
過去幾年,web安全研究者之中通用的認識中js文件,jsonp, json,或者版本較老的瀏覽器都有可能受到這種方式的攻擊,除此之外,還可以通過一些瀏覽器的漏洞來得到js的錯誤信息利用,不過目前應該已經修復的差不多了。
2014,我們針對這個技術進行了專門的研究,發現了一些有趣的利用技術和瀏覽器漏洞,可以獲取一些簡單的文本中的信息,比如csv,在一些特定的情境下還可以獲得更復雜的信息。我們主要的研究方向在于通過客戶端腳本去識別目標數據的方法,比如變量,或者函數名。
下一節會開始介紹利用技術,最后會談論下防御的手段。
我們總共發現了5種與xssi相關的漏洞利用技術,或者是瀏覽器漏洞。
為了防止js錯誤信息跨域泄漏,對于外部加載的js文件,現在主流的瀏覽器只有固定的錯誤信息,比如“script error”,當是在ie9與ie10,情況不一定如此。
一般來說,在外部js發生語法錯誤的情況下,瀏覽器只會提供固定的錯誤信息,但是當在runtime發生錯誤的情況下,瀏覽器會提供詳細的錯誤信息。比如"foo 未定義"之類的,某些瀏覽器一旦允許外域js回復詳細的錯誤信息,就會導致信息泄漏。
就是說,當某個網頁的內容能被js識別為javascript格式的話,那么就可能通過錯誤信息獲取到目標的內容。
比如,目標網頁
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="a.csv"
Content-Length: 13
1,abc,def,ghi
攻擊者設置錯誤顯示
#!html
<SCRIPT>window.onerror = function(err) {alert(err)}</SCRIPT>
<!-- load target CSV -->
<SCRIPT src="(target data's URL)"></SCRIPT>
一旦加載成功,網頁則會顯示 "'abc' is undefined" 。
會出現這種情況是因為瀏覽器將目標識別為javascript,那么abc就會被識別為某個未定義的變量。當為這種情況的時候,瀏覽器就允許頁面捕捉來自不同網頁的錯誤信息。
做一個總結就是,有被利用的可能性的數據都是可以被識別,或者通過某種方式識別為有效js的數據。
我們在2014年7月報告這個問題,分配MS14-080,后來的 CVE-2014-6345也同樣被分配到這個bug (1](2] 他們的修補方案跟其他的瀏覽器是差不多的,把錯誤信息改成某個固定的信息。
不過,稍微需要注意的一點,出現該漏洞的只有ie 9 和 ie 10.
遺憾的是我們并不是最早注意到這個問題的貨,08年的時候,安全研究人員Chris Evans就在firefox (3]中發現了類似的問題,不過他的攻擊代碼看起來好麻煩的樣子,這貨利用多重重定向來欺騙瀏覽器。另外一件事就是13年的時候,研究人員Yosuke Hasegawa 和 @masa141421356也做過相關的研究,(4]
大家可以看到,上面的東西只在csv這種操蛋的玩意上有用, 所以我們做了更多的研究看看能否獲取不同格式的數據,之后我們發現通過UTF-16編碼可以達到我們的目標。
其實本身是一個很簡單的技巧 比如頁面a ,我們加入 charset="UTF-16BE"
#!html
<!-- set an error handler -->
<SCRIPT>window.onerror = function(err) {alert(err)}</SCRIPT>
<!-- load target JSON -->
<SCRIPT src="(target data's URL)" charset="UTF-16BE"></SCRIPT>
然后json數據長這個逼樣
HTTP/1.1 200 OK
Content-Type: application/json
Content-Disposition: attachment; filename="a.json"
Content-Length: 39
{"aaa":"000", "bbb":"111", "ccc":"222"}
當響應缺少字符集規范的時候,會被charset屬性強制轉碼為固定的編碼,我們用這個技巧擼掉了許多有名的瀏覽器,包括ie 9。
測試這段代碼之后,我們給自己彈了個窗。
我們可以看到一串亂碼,因為,當瀏覽器獲取目標網頁的數據,之間經過了一次編碼,然后到我們的頁面上經過charset制定的字符集進行了一次解碼。
我們能很簡單的得出一個結論就是我們能通過對亂碼的再次編碼來獲得原有的信息,不過需要注意的就是只有當編碼后的信息能夠被瀏覽器識別為有效的js標示符的時候攻擊才有可能成功,這是一個重要的條件,對于不同的平臺的編碼是有所不同的,在ie上可以被識別為有效js標示符的字符是多于其他平臺的,至于其他來說ie的 ECMAScript規范 (5]跟其他瀏覽器總體沒什么不同。
打個比方對于ie來說 '3q' (U+3371, ?) 在 unicode編碼中會被認為是 屬于 "Symbol, Other [So]",就是符號的一種。總的來說這種形式的認定不應該發生在任何瀏覽器中,不過ie可能比較2b一些。
我們花了很多時間研究了什么樣的組合,能夠被瀏覽器認定為有效的js標示符,當字符編碼為UTF-16的時候的數字字母組合,ie 9將其99.3%認為是有效的js標示符,高于chrome和firefox。具體結果見下圖
需要注意的一件事就是在ie 10 或者更高的版本,可能攻擊無法奏效,因為ie 10 拒絕將沒有空字節活著bom的編碼為utf16。
Harmony是一個ECMAScript 6中的新功能 (6] ,類似于java的反射類,其中定義了對于對象屬性的查找,分配,函數調用,在我們針對這些新特性的研究過程中發現該功能可以用于xssi的攻擊中。
for example:
#!html
<!-- set proxy handler to window.__proto__ -->
<SCRIPT>
var handler = {
has: function(target, name) {alert("data=" + name); return true},
get: function(target, name) {return 1}
};
window.__proto__ = new Proxy({}, handler);
</SCRIPT>
<!-- load target CSV -->
<SCRIPT src="(target data's URL)"></SCRIPT>
注意其中的window.proto 定義了一個代理對象,當訪問一個未定義的全局變量,就會出發handler進行處理。
然后csv文件長這樣:
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="a.csv"
Content-Length: 13
1,abc,def,ghi
當訪問攻擊頁面的時候如果攻擊成功那么久會收到 "data=abc", "data=def", "data=ghi"的彈窗,我們在firefox和chrome都得到了驗證。
我們在去年八月報告該bug,同一時間chrome 的js代理被默認關閉,需要通過設置開啟 (chrome://flags/#enable-javascript-harmony),后來在15年1月 (7],該功能被從chrome中分離。該bug被分配cvs編號 CVE-2014-7939 (8](9]
對于firefox ,在我們還在專注寫報告的時候,firefox卻公布了這個bug (10],原因是一個叫Erling Ellingsen的貨發現了這個bug然后發到twitter上(11],目前該bug還沒修復,當然也沒有cve。
不過倒是推薦關注下 firefox 的bug跟蹤版(7] (10],這是否真的算個安全漏洞確實值得討論,or只需要將其當成js功能的一種,另外一個事實就是,即使沒有這玩意我們也可以通過對外部文件的窮舉來攻擊兼容js語法的文件。
下面我們會討論關于窮舉的攻擊方式。
假設一個攻擊頁面通過js 加載了下面的csv文件。
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="a.csv"
Content-Length: 8
1,xyz123
一旦加載我們就會得到一個 xyz123未定義的錯誤,換句話說,如果我們在加載外部文件之前定義了這個標示符,那么我們就不會受到這個錯誤,同時我們也可以判斷xyz123是存在于外部文件中的。也就是說我們需要一個合適的檢測錯誤是否發生的方式。一般情況下瀏覽器是不提供詳細的外部錯誤信息,不過仍然會返回一個通用的錯誤標示。所以說窮舉信息還是是存在可能性的。
總的來說我們發現三種窮舉的方式
第一種是二元搜索。比如你知道目標會是 "xyz121", "xyz122", "xyz123" 和 "xyz124"中的其中一個,可以先定義前兩個變量然后看有無錯誤爆出,然后定義后兩個,然后再縮小目標。
第二種是使用 js 的getter,像下面醬紫
#!html
<!-- set getters -->
<SCRIPT>
Object.defineProperty(window, "xyz121", {get: function() {alert("value=xyz121")}});
Object.defineProperty(window, "xyz122", {get: function() {alert("value=xyz122")}});
Object.defineProperty(window, "xyz123", {get: function() {alert("value=xyz123")}});
Object.defineProperty(window, "xyz124", {get: function() {alert("value=xyz124")}});
</SCRIPT>
<!-- load target CSV -->
<SCRIPT src="(target data's URL)"></SCRIPT>
就是目標值訪問 window.***||||||* 會觸發上面的規則。
第三種是使用vbscript來獲取json數組,這個思路來自Hasegawa做的研究,組合vbscript和json進行攻擊(4]
目標頁面長這個樣子
HTTP/1.1 200 OK
Content-Type: application/json
Content-Disposition: attachment; filename="a.json"
Content-Length: 12
[1,"xyz123"]
然后再我們的攻擊界面中調用vbscript
#!html
<SCRIPT language="vbscript">
Sub [1,"xyz121"]: MsgBox "value=xyz121": End Sub
Sub [1,"xyz122"]: MsgBox "value=xyz122": End Sub
Sub [1,"xyz123"]: MsgBox "value=xyz123": End Sub
Sub [1,"xyz124"]: MsgBox "value=xyz124": End Sub
</SCRIPT>
<!-- load target JSON as VBScript -->
<SCRIPT src="(target data's URL)" language="vbscript"></SCRIPT>
跟上面的攻擊相似,都是通過窮舉來獲取目標值。不過vbscript只試用于ie。
ps: 怎么說呢。。。我覺得好蛋疼。
上面獲取csv的信息只在目標的字符串沒被引號擴起來的情況下,不過同樣是一些小技巧能夠使我們繞過這一限制。
讓我們假設一個csv長這個b樣。
1,"___","[email protected]","03-0000-0001"
2,"foo","[email protected]","03-0000-0002"
...
98,"bar","[email protected]","03-0000-0088"
99,"___","[email protected]","03-0000-0099"
假設攻擊者能夠插入自己的字符串,那么只需要根據RFC相關CSV (RFC 4180 (12])中的規定來添加一個雙引號就可以bypass這個限制。
for example
1,"\"",$$$=function(){/*","[email protected]","03-0000-0001"
2,"foo","[email protected]","03-0000-0002"
...
98,"bar","[email protected]","03-0000-0088"
99,"*/}//","[email protected]","03-0000-0099"
一個比較蛋疼的問題就是如何獲取多行的信息,因為多行在js中是違法的,上面的例子里,我們使用 $$$=function() {/…/}來進行攻擊,然后攻擊者可以調用$$$.toString() 獲取函數遠嗎來達到攻擊目標數據的目的。這種攻擊方式試用于所有的瀏覽器。
一種獲取多行內容的方式可以在chrome和firefox中奏效,就是ECMAScript6模版字符串中通過反引號來獲取多行內容。
下面是需要注意的一些問題。
上面我們演示了xssi通過組合瀏覽器漏洞或者一些攻擊技巧來到達獲取一些特定數據的目的,不過其利用場景還是具有局限性的。
其防御的方式只需要設置響應頭圍X-Content-Type-Options: nosniff ,那么瀏覽器就會拒絕記載這種類型的數據為js。
同時還需要設置字符集規范,來防止一些特殊場景的攻擊,從這里可以看到一些關于字符集攻擊的參考(13]
攻擊并不局限于某種瀏覽器。
很蛋疼的一個問題 X-Content-Type-Options 頭只適用ie-8+ 和chrome,并不包括其他瀏覽器。
firefox還在討論要不要這么搞。(14](15]
總之建議使用 Content-Type 和 X-Content-Type-Options。
這里有一些其他的措施
禁止get請求
使用一些難以猜測的參數
使用自定義頭時使用XHR進行請求。
或者說只是拒絕不滿足條件的http請求,簡單來說設置一些過濾規則進行攔截。