XSS和字符集的那些事兒
0x00 前言
在文章的開頭,我想對上次釋出了一個結論及其離譜但還算及時被刪除了的 文章(關於跨域字符集繼承的那篇)道個歉。也希望沒有測試就去轉載的那些人,可以把那個文章刪除了。防止更多人對跨域字符集產生了錯誤的理解。不過作為謝罪,我也又重新整理了一篇文章,這也是一直以特別想寫的一篇。但是覺得這個題目對我來說還是有點大,所以就一直沒有下的去手。不過吹過的牛逼早晚都是要兌現的,早死早超生,所以就硬著頭皮去寫吧>.<
。我也不扯 那些字符集是什麼之類的了,讓我們透過一個接一個的例子一起進入 XSS 和字符集所創造的世界吧。
0x01 基於 UTF-7 的 XSS
在開始之前,先對 UTF-7 做一個簡單的介紹吧。UTF-7 是可以將所有的 unicode 透過 7bit 來表示的一種字符集。早期多數被利用在郵件環境當中,但現在已經從 Unicode 規格中移除。這個字符集為了透過 7bit 來表示所有的文字, 除去數字和一部分的符號,其它的部分將都以 base64 編碼為基礎的方式呈現。 比如:
#!html
<div> 我了個去!</div>
用 UTF-7 表示,就是:
+YhFOhk4qU7v/AQ- +Adw-/div>
同樣的,
#!html
<script> alert("xss") </script>
就會變成:
從上面的例子當中,不難看我們的程式碼中並沒有出現我們期待的那種形式的” <”,”>”
或雙引號。但是我們要怎麼將這種情況和 XSS 聯絡起來呢?大致的情 況可以分為 3 類:
(1)我們沒有透過Response header或Meta標籤來設定字符集
#!html
<html>
<head><title>test page</title></head>
<body>
</body>
</html>
一種情況是,IE 的編碼設定為自動檢測,IE 就會跟據一些 BOM 字元,比如 p>
另外一種情況就是雖然 IE 沒有勾選自動檢測字符集的設定,但是我們可以透過製作一個字符集為 UTF-7 的頁面,並透過 Iframe 來包含我們的目標頁面, 透過字符集繼承漏洞來實現字符集的設定。
<meta http-equiv='content-type' content='text/html;charset=UTF-7'>
<iframe src='http://example.com/target.html'></iframe>
不過遺憾是在,在現在已經沒有這種基於 iframe 的跨域字符集繼承的漏洞 可利用了。MK 在不久前也指正了某人在 Slide 中這種的錯誤。
簡短的翻譯一下,就是說:『如果在你的 Slide 中所說的透過 iframe 來設定字符集是指,繼承 top frame 的字符集,那麼現在已經不存在這種問題了。因為這種繼承的大前提是必須同域。』
(2)我們設定了一個無法識別的字符集
其實在一篇被刪除的文章當中(筆者的測試方法有問題,得到結論都是錯誤的所以自覺提出刪除),/fd 同學表示 utf8 也是標準。但它以前可不是標準。但是為什麼 utf-8 和 utf8 都變成了標準呢?因為總會有粗心的人犯下這樣的錯誤,比如:
把 UTF-8 寫成 UTF8
把 EUC-JP 寫成 EUC
 這種設定方法在以前是無法被瀏覽器所識別的。換句話來說就和沒有設定字符集是一樣的。具體利用方法可以參考第一種。
(3)輸出點在
標籤之前,且字符集是由 meta 標籤所指定的
大概的場景可以像這樣(輸出點在 title 內,meta 之前):
#!html
<html>
<head>
<title>輸出</title>
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
</head>
由於上述的 BOM 和基於 iframe 的字符集繼承現在都已經不能利用了。所以在情況三下我們可以考慮先插入一個
#!html
</title><meta charset=utf-7>
0x02 基於 US-ASCII 的 XSS
其實基於 US-ASCII 的 XSS 和基於 UTF-7 的 XSS 有很多的類似之處。它也同樣是透過 7bit 來表示數字和少數符號的字符集。可以用來表示從 0x00 到 0x7F 的 128 種文字。但如果你試圖用 Internet Explorer 來開啟一個內容是透過 US-ASCII 來記述的文件時,你會發現這個字符集不單是隻會解析從 0x00 到 0x7F 的文字。即使是 0x80 到 0xFF 這個範圍中無法透過 7bit 來表示的字元,也會透過忽略最上位的bit的方法生成一些和0x00~0x7F等價的字元。
也就是說在這個字符集當中:
雙引號 0x22 等價於 0xA2
左尖括號 0x3C 等價於 0xBC
右尖括號 0x3E則等價於0xBE
比如,你把下面的這一段透過儲存成 html,編碼選 shift_jis(如果是記事本的可以用 ANSI)
#!html
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=us-ascii"> </head>
<body>
シ script セ alert(document.charset)シ/script セ
</body>
</html>
註釋:シ和セ在 shift_JIS 當中分別是 0xBC 和 0xBE
然後,再用 Internet Explorer 開啟它,那麼最終你會看到小視窗彈起來了。
0x03 利用字符集來繞過 htmlspecialchars()函式
在看完前面的兩個字符集後,我們會發現這兩個字符集的一個共同點,就是 都沒有出現”<”,”>”
或雙引號。這不難讓我們聯想到 PHP 中 htmlspecialchars()的 繞過。雖然接下來會提到的 iframe 跨域字符集繼承漏洞已經不能再利用了,但是在下文中我會提出可以復現這個漏洞的具體環境。
這是在 kotowicz 的部落格當中 2010 年提到的一個 XSS hackme challenge 的解決方案。challenge 的主要目的就是繞過 htmlspecialchars()這個函式來實現 XSS。 而且重要的是這個頁面並沒有透過 response header 或 meta 來設定字符集。
筆者一開始提到可以做出這樣的一個 POC:
#!html
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-7">
</head>
<body>
<iframe width=500 height=600 src="http://kotowicz.net/shoutbox/shoutbox.php"></iframe>
</body>
</html>
大概意思就是在 IE6 那個年代,字符集繼承問題橫行,我們只需要一個 iframe 就能完成這個挑戰(我沒有在 playonlinux 裡的 IE6 裡復現成功,所以可能你需要足夠古老的環境來重現這個問題)。筆者提到對於 IE8 來說,我們只能繼承同域的字符集(iframe)。但是那個時候有一個小的 BUG 可以用來欺騙瀏覽器。
大致思路如下:
#!html
// utf7exploit.html
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-7">
</head>
<body>
<iframe width=500 height=600 src="redirect.php"></iframe>
</body>
</html>
// redirect.php
<?php
header("Location: http://path.to/shoutbox.php");
?>
包含一個同域的檔案,並在那個檔案裡透過 header(Location:somedomain)來進行跳轉進而繞過不同域 iframe 不能繼承字符集的限制。(>.<
那是個多麼美好的年代啊)。不過遺憾的是後來這個漏洞也被補了。如果你想親自體驗一吧。可能需要你在winxp sp2+ IE7 的環境下進行復現。
0x04 不是所有的跨域字符集繼承都得用 iframe
在我們對跨域的字符集繼承問題的嚴重性有了一定的瞭解之後。讓我們再看看日本的猥瑣流是怎麼玩的吧。這是 MK 在完成 ZDResearch 出的一個 XSS 挑戰時所用到的一個漏洞。(CVE2013-5612)
在 Firefox26 之前的版本下,如果對沒有進行 charset 設定的頁面透過 POST 傳送請求,那麼即使是在不同域的情況下也會繼承傳送頁面的 charset 導致可以被 XSS 攻擊所利用。換句話來說,我們可以從任意頁面傳送 post 請求,來對沒有設定 charset 的頁面,進行 charset 的任意設定。
下面是 ZDResearch 的 XSS 挑戰(沒有設定 charset):  https://zdresearch.com/challenges/xss1/
這是 MK 構造的 POST 頁面:
http://l0.cm/zdresearch_xss_challenge.html
具體的 POC 程式碼如下:
#!html
<meta charset="iso-2022-kr">
<form action="https://zdresearch.com/challenges/xss1/" method="post">
<input name="XSS" value="<h1 a=>onmouseover=location='jav\x41script\x3Aalert\x28"MK"\x29' >xxx">
<input type="submit" value="go">
</form>
當我們透過 charset 為 iso-2022-kr 的頁面向 https://zdresearch.com/challenges/xss1/傳送 POST 請求時,目標頁面會繼承我們的 charset。由於字符集[ISO-2022-KR]中會把以
開頭
結尾的一串字元看作是 2 bytes 也就是一個字元導致最終成功的 插入 onmouseover 到目標頁面當中。兩個字......漂亮!
0x05 霸道流(MS13-037)
寫到這裡 iframe 也玩過了,POST 也試過了。還有別的?當然有!還是來自 MK 部落格中的一篇文章,【強制喚起 Internet Explorer 的自動檢測編碼功能】。
測試環境:Windows Vista sp2 IE9
重現方法:透過製作特定的頁面來強制喚起自動檢測編碼功能
#!html
<script>
function go(){
window.open("http://vulnerabledoma.in/r_slow?url=http://target/","x")//順序 1
window.open("http://vulnerabledoma.in/h_back.html","x")// 順序2
}
</script>
<button onlclick=go()>go</button>
因為這些頁面都是存在的,所以感興趣的話,你可以自己一個一個的開啟看看裡邊都寫了一些什麼。其中可能比較讓人沒法理解的地方是順序 1 裡邊的跳 轉會有一點延遲。可以訪問這個頁面自己感受一下 http://vulnerabledoma.in/r_slow?url=https://www.google.com/ 這是因為如果在目標頁面還沒有被完全載入完之前就嘗 試去返回無法實現亂碼(mojibake)。然而稍微加點延遲再跳轉就可以完美的解決這個問題。
亂碼了是安全問題麼?也許是的。因為在這種情況下你只要在目標頁面的某一個輸出點製造一個某字符集才特有的輸出,那麼根據這個 POC,目標頁面會 根據你特定的輸出而變換字符集最終導致漏洞的產生。(比如包含[0x1B]$)C 就有可能讓頁面的編碼成為 ISO-2022-KR)。最終,MK 也透過這一方法從 Google 的口袋裡得到了 500 刀。別人拿了多少錢對我們來說固然不重要,但這應該可以從側面證明這種漏洞的可行性和價值。
0x06 CSP繞過
字符集似乎什麼都能幹。繞過了 htmlspecialchars 函式,幫助駭客們刷 了一個又一個的 CVE,還順帶完成了一些挑戰整了點奶粉錢。但是字符集能做 的還遠不及這些。就讓我們再來看看這個基於字符集的 CSP 繞過吧。(依然來 自 MK 的部落格)
當然使用這種方法有一些先決的條件:
• HTTPResponseHeader沒有設定charset • 允許我們在目標頁面內植入 0x00 • 我們的輸入在將位於輸出點之前的文字轉成 UTF-16(BE/LE)時,那段字串可以做 javascript 的函式來使用
這個場景是不是有點太挑剔了呢,哈哈!所以我自己做了一個這樣的頁面。
你可以假想一下這是一個存在儲存型 XSS 漏洞的頁面,並且被植入了一些 script。
下面是具體的 POC 頁面: http://vulnerabledoma.in/csp_utf16
烏雲社群裡/fd 發起的 CSP 挑戰也和這個有很大的相似之處。最終/fd 也在帖子 裡給出了自己的解決方案。感興趣的話,可以去看看。  http://zone.wooyun.org/content/10596
0x07 字元中的“幽靈”
有時候字元就像幽靈一樣,我們並不能感覺到它的存在。比如老版本的 Firefox 會忽視 0x80,老版本的 IE 會忽視 0x00。這無疑是個讓人很頭疼的問題, 因為對於過濾器來說 script 可不等於 s[0x00]cript
。又比如在 chrome 當中會忽略一些位置上的字元(這個現在也能用):
<a href="javascript:alert(1)">asd</a>
有時候它不但會默默的存在,而會去破壞一些什麼,比如下面的例子:
#!html
<html>
<head>
<title>testsuite</title>
<meta charset="gb2312">
</head>
<body>
<script>
var q="<?php echo str_replace("</","<\/",addslashes($_GET["test"])); ?>"; </script>
</body>
</html>
如果我們運用寬位元組就可以突破這裡的限制:

但這種問題,只會出現在 GBK 裡面麼?其實這樣的字符集還有很多。下面就是一個 Shift_JIS 的例子。將上面的程式碼中的字符集改成 shift_JIS 後的測試結果如下:
有問題的字符集還遠遠不止這些。在這次東京舉行的 OWASP 國際峰會上,日本人 Masato kinugawa 的議題“編碼和安全的徹底調查”當中就提交到了很多這樣 的問題。下面對這個議題的內容做一下簡單的介紹。
(1)各瀏覽器對字符集支援情況的調查
作者事先收集了將近 2500 個左右像字符集編碼名稱的文字,並透過測試,對結果進行了三種分類。分別為字符集名,別名和無法識別的字元。其中的調查方法細節可以參考下面的連結:
http://masatokinugawa.l0.cm/2013/03/browser-‐support-‐encodings-‐list.html
經測試發現瀏覽器正在支援許多平時不回被用到的字符集。下面是字符集正式明和別名的一覽:
然後是各瀏覽器對字符集的支援情況:
由於內容比較多,這裡只附上部分貼圖(上圖為 chrome 的支援情況): 下圖為 IE 的支援情況:
就像前面所提及到的 MK 也認為其中最兇殘的還是 UTF-7。因為一般的過濾方法根本無法攔截這種 XSS 攻擊。而且悲催的是直到 IE11 微軟還依然在支援著這個編碼。不過好訊息是微軟正在探討是否會在接下來的 IE12 當中移除對 UTF-7 的支援。
在完成了對各個瀏覽器的編碼支援情況的調查之後,MK 又對這些編碼進行了各種各樣的測試。
將歷史上出現過問題的部分作為參考,對字符集進行下面三種測試:
{TEST1} 特定的 byte 最後會變成特別的字元。
{TEST2} 特定的 byte 會破壞緊隨其後的文字。
{TEST3}特定的 byte 會被忽略。
TEST1 的部分測試結果:

註釋:其中第一列位瀏覽器,第二列為字符集,第三列為測試的 byte,第四列為呈現的字元。
TEST2 的部分測試結果:
註釋:其中第一列位瀏覽器,第二列為字符集,第三列為具有破壞性的 byte,,第四列具體破 壞的 byte 數。
TEST3 的部分測試結果
註釋:其中第一列位瀏覽器,第二列為字符集,第三列為會被忽視的 byte
如果想檢視這三項測試的完整結果,可以看這裡:http://l0.cm/encodings/
最後讓我們來看看使用這些字符集特性都可以幹一些什麼吧.
(1)繞過瀏覽器的 Anti-XSS 功能
Chrome 的 Anti-XSS 功能繞過例項:
IE 的 Anti-XSS 功能繞過例項:
(2)基於編碼切換的 self-XSS
註釋:具體操作方法就是手動切換編碼至 shift_jis(具體可以翻閱前面給出的 shift_jis 的例子)
雖然這不算是漏洞,但這依然是一個問題。這種問題和你有沒有設定字符集無關。而且對於這種問題,很難去進行應對。對於一般使用者來說,他們根本無法想象只是因為自己切換了編碼,就有可能被攻擊。但是,值得慶幸的是 NoScript 可以檢測出這種問題^_^。
0x08 總結
可以看到一個簡單的字符集,可能產生各種你意想不到的問題。雖然本文一直在圍繞著 XSS 去闡述其可能產生的安全問題。但是我們都知道實際上的影響面可不止這一些。作為廠商我覺得也應該重視起這個問題,儘量在所有的頁面都設定 charset,條件允許的情況下最好是透過 HTTP Response Header,而不單是透過 meta 標籤。作為使用者,向上述的基於編碼切換的 XSS 問題也應該引起重視。如果你是 Firefox 使用者,建議安裝 NoScript 進行防禦。不要輕易去聽信他人的誘導在一些頁面上做切換編碼的操作。文中如果出現了錯誤,還希望大家能指出來。這樣可以讓我學到更多,也可以防止更多的人學到錯誤的東西。
參考文獻
http://gihyo.jp/admin/serial/01/charcode http://blog.kotowicz.net/2010/10/xss-hackme-challenge-solution-part-2.html http://www.slideshare.net/ockeghem/owasp20134021-x https://speakerdeck.com/appsecapac2014/the-complete-investigation-of-encoding-an d-security http://masatokinugawa.l0.cm/2013/12/CVE-2013-5612-encoding-inheritance-xss.htm l http://masatokinugawa.l0.cm/2013/11/MS13-037-encoding-xss.html http://masatokinugawa.l0.cm/2012/12/encoding-self-xss.html http://masatokinugawa.l0.cm/2012/05/utf-16content-security-policy.html
相關文章
- XSS與字元編碼的那些事兒 ---科普文2020-08-19字元
- webpack的那些事兒2019-05-12Web
- https的那些事兒2019-02-21HTTP
- PHP那些事兒2019-02-16PHP
- Redis那些事兒2019-02-16Redis
- babel那些事兒2019-03-14Babel
- Eval家族的那些事兒2019-03-30
- Python和單元測試那些事兒2018-08-15Python
- 漏洞檢測的那些事兒2020-08-19
- 關於 sudo 的那些事兒2019-12-19
- 面試的那些事兒--012021-03-10面試
- 雲原生java的那些事兒2019-03-01Java
- util.promisify 的那些事兒2018-10-17
- iOS 截圖的那些事兒2018-06-03iOS
- HTTP 快取的那些事兒2018-08-21HTTP快取
- 聊聊Django應用的部署和效能的那些事兒2020-07-26Django
- h5 和native 互動那些事兒2018-11-06H5
- 說說RCE那些事兒2020-08-19
- C語言那些事兒2020-04-04C語言
- 字元編碼那些事兒2021-09-09字元
- MySQL優化那些事兒2019-03-02MySql優化
- 網路安全那些事兒2018-11-08
- PHP 閉包那些事兒2019-02-16PHP
- 綠帽子水管工的那些事兒2019-10-15
- Filebeat 收集日誌的那些事兒2020-06-18
- [apue] 等待子程式的那些事兒2019-07-08
- 聊聊瀏覽器的那些事兒2019-02-15瀏覽器
- 我與軟考的那些事兒2018-03-25
- 使用 etcd 和 grpc 遇到的版本衝突的那些事兒2020-04-26RPC
- 法線貼圖那些事兒2020-05-25
- 「前端那些事兒」④ 效能監控2019-04-01前端
- 程式碼重構那些事兒2019-02-03
- Node檔案操作那些事兒2018-03-20
- Elasticsearch(ES)分詞器的那些事兒2021-09-19Elasticsearch分詞
- 深入淺出Mysql索引的那些事兒2019-08-22MySql索引
- Docker容器中應避免的那些事兒2020-06-26Docker
- 有關指標的那些事兒《一》2020-11-14指標
- 談談java入門的那些事兒2020-09-28Java