阿碼外傳-阿碼科技非官方中文 Blog: 誰在看我的噗?第三回:弔詭的過濾函式

2009年7月17日

誰在看我的噗?第三回:弔詭的過濾函式


(本文感謝 armorize parser 團隊 Chris, Eric, Steven, Wing 與 ASF 團隊 Kuon 的意見)

泡咖啡或茶的藝術,我稍微接觸過一些,很有意思,每一個部分都很講究,從水、豆子或茶葉、到器具、時間等,每一個環節的掌握,決定了最後享受的品質。

有在品嚐的朋友都知道,其中常被忽略但是卻是關鍵者,就是過濾方式。以茶來說,不好的濾網可以直接喝到金屬的味道,所以有些人會把沖泡器的濾網拆除,寧可茶中有些茶葉。以咖啡來說,紙濾網會有味道,另外吸油能力太強,會讓咖啡少了原本的濃郁。很多人因此喜歡用金(或K金)濾網,雖然貴,但是金穩定,比較不會有異味,泡出來的咖啡跟紙濾網喝起來完全不一樣。可是金濾網除了價格高以外,很多廠商的技術不到位,濾得不乾淨,常讓咖啡渣漏到咖啡中,那就大大破壞興致了。就連自己做豆漿,最難的部分還是過濾機制的設計。要做一個好的過濾器,並不容易。

在Web資安中亦是如此。前面我分享了「誰在看我的噗?第一回」與「誰在看我的噗?第二回」兩篇,裡頭提用了噗浪的一些跨站腳本攻擊(XSS)漏洞來做範例。當初噗浪雖然有過濾CSS中的「expression」,但是改其中一個字的大小寫,例如「eXpression」,就繞過噗浪的過濾機制了。

回報給噗浪後,改好了,我這次,恩,的確改好了,現在不論是「Expression」、「eXpression」或「exPression」,都會被阻擋起來。於是我就試了下面這個字串:

「ex/**/pression」

IE的CSS parser,會將「/*」與「*/」當成是註解,將中間的字串都拿掉,於是「ex/**/pression」還是變成了「expression」,會執行javascript。測試發現噗浪並沒有偵測這種形式(pattern),於是又回報他們。以下是一些可能的變形:

「ex/**/pression」、「ex/**/p/**/ression」、「e/**/x/**/p/**/r/**/e/**/s/**/s/**/i/**/o/**/n/**/」

噗浪改好後,我測試,嗯,真的改好了,以上這些形式都會被阻擋起來,確定改好了。接下來我測試「ex/*armorize*/pression」,發現繞過了噗浪的檢查。原來噗浪沒有注意到,「/*」與「*/」中間可以加任何的字串,於是又回報他們。以下是一些可能的變形:

「ex/*armorize*/pression」、「e/*A*/x/*B*/p/*C*/r/*D*/e/*E*/s/*F*/s/*G*/i/*H*/o/*I*/n」、「e/*/*/*****///"hello"*/*/*/xpression」

噗浪改好後,我測試,嗯,真的改好了,以上這些形式都會被阻擋起來,確定改好了。接下來我想,該不會噗浪的過濾機制,是以「行」為單位的吧?於是我測試:


ex/*
*/pression

中間換行,發現又繞過去了。註解中間雖然有換行,IE還是會執行expression後面帶的javascript,可是噗浪是以「行」為單位進行過濾,所以又被我繞過去了。於是我又回報他們。以下是一些可能的變形:

ex/*
armorize
*/pression

ex/*
XSS**/**/*/pression

噗浪改好後,我測試,嗯,真的改好了,以上這些形式都會被阻擋起來,確定改好了。這是我仔細想,噗浪是如何做過濾機制的呢?最直覺的作法,應該是用正規表示法(regular expression)來過去,並採以下步驟:

1. 以整個CSS為單位,移出「/*」與「*/」以及中間的字串(因為是註解)
2. 偵測「expression」的各種大小寫形式

恩,那麼,如果我用以下的一段CSS呢?

X{"/*"} expression() X{"*/"}

上面這樣的CSS,對於IE來說,由於文法是可以接受的,另外「/*」與「*/」被包在雙引號內,所以IE不會視為是註解。可是對於此時噗浪的過濾機制來說,過濾機制的第一步會將「/*」與「*/」以及中間的字串移出,所以經過第一步後,噗浪看到的是:

X{""}

沒有「expression」的存在,故判定是合法沒有惡意的CSS而接受。實際上測試,正是如此,而我這個新的CSS攻擊,又繞過了噗浪此時的過濾機制。於是趕緊回報噗浪,噗浪改好後,我測試,嗯,真的改好了,這種形式都會被阻擋起來,確定改好了。可是我仔細一想,不對,目前最新的過濾機制應該是這樣,那麼如果我寫這樣,應該又可以繞過,結果果然。於是我回報,不過到這次,已經不是那麼容易可以改了,甚至要考慮一下以正規表示法(regular expression)為基礎的CSS過濾機制,到底有沒有辦法做到安全?過了兩星期問題還是沒修好,我這邊就不探討實際的字串了。

算一算到目前為止,攻擊已經變形了七代了。如同Twitter Mikeyy蠕蟲變形了多代一樣,如果今天同樣有駭客要攻擊噗浪,那麼已經變形七代了,可以做七波的攻擊,每一次攻擊都可以是蠕蟲或是偷受害者的cookie(然後以該身份登入噗浪)。另外很有趣的是,噗浪的第二代過濾機制,其實是可以阻擋第六與第七代的變形攻擊的,因為該機制只是單純地檢查有無「expression」的各種大小寫形式存在。可是之後,噗浪為了要應付第三代攻擊形式(「ex/**/pression」),而導致改進後的過濾機制反而會被第六與第七代攻擊形式繞過(X{"/*"} expression() X{"*/"})。

從噗浪的一系列攻守中我們可以獲得以下結論:

1. 設計一個正確的過濾機制是不容易的。找到漏洞不表示可以正確修補。
2. 修補的過程中,很有可能導致新的漏洞產生。


在這邊順便問一下,有沒有網友有興趣寫一個CSS的過濾函式?如果願意與我們一起研究,我們可以一起公開一套針對CSS的開放源碼過濾函式。

CSS的過濾函式,真的可以用單純的正規表示法(regular expression)之字串比對機制來完成嗎?

我們先來研究一下,到底IE是如何處理CSS的。第一,「ex/**/pression」==「expression」到底是否合於CSS2的文法(grammar)與語意(sematics)?我們看一下W3C對於CSS2.1的定義中,註解(comments)這段

Comments begin with the characters "/*" and end with the characters "*/". They may occur anywhere between tokens, and their contents have no influence on the rendering. Comments may not be nested.

「註解以 "/*" 開始,以 "*/" 結束,可以存在於任兩個 token 之間...」既然是任兩個token之間,那麼「ex/**/pression」根本不應該等於「expression」,而要被分成「ex」與「pression」兩個token。實際上W3C對於CSS2.1的文法定義也反映了這點。

如果是單純用標準的lexer與parser來處理CSS的話,ex/**/pression一般來說會被切成兩個token,而要認識「ex/**/pression」為「expression」的話,需要特別花功夫重組token。我認為IE不是這樣做的。我認為IE目前的CSS處理模組,包含了一個或多個的preprocessor,然後才餵給一組lexer與parser。前面的preprocessor可能也是用lexer與parser組成,但更可能只是用正規表示法(regular expression)之字串比對機制來完成。這樣的設計造就了在IE裡,「ex/**/pression」==「expression」的錯誤。

可是說IE錯了,沒有用,因為「ex/**/pression」在IE中就是會執行javascript,攻擊就是會成功。所以我們這篇探討的過濾機制,重點變成了,「如何了解各家瀏覽器對於CSS的處理機制,並實做一個模擬函式,來做資安過濾」。

這個就難了,因為各種歷史因素加上一開始CSS的定義並不明確,各家瀏覽器對於CSS的處理可說各顯神通,怪招一堆。舉個例子,雖然CSS中並無定義,但是以往非IE瀏覽器(FF、Safari、Chrome)對於「//」這個註解掉本行的語法,也是支援的;IE則不支援。可是到了IE8時,突然決定在「IE8標準模式」下時,跟其他非IE瀏覽器一樣,支援「//」。於是以下CSS,用非IE瀏覽器瀏覽,以及用IE8在「IE8標準模式」下瀏覽,背景會是紅色的(支援「//」),但是用IE<8或IE8在「IE7標準模式」瀏覽,背景會是藍色的(不支援「//」):

body {
background-color:red;
// background-color:blue;
}

那麼我們在寫過濾器時,到底要不要把「//」當成行註解呢?

又舉一個例子,以下這個CSS,在IE5/MAC,以及在IE8 beta 1上,中間的「@import」會被執行,但是在其他瀏覽器,包含IE8正式版中,則會被當成註解:

/*\*//*/
@import...
/**/

這主要跟lexer/parser或proprocessor在處理CSS時所採用的優先順序(operator precedence)有關。IE5/MAC與IE8 beta 1 是如此解讀的(資料來源:stopdesign.com

(1)的「/*」被視為是註解的開始,(2)的「\」將(3)的「*」給escape掉了,所以一直到(4)的「*/」才被視為是註解的結束。(5)的@import在註解外,而(6)的「/*」與「*/」被視為一對註解的開始與結束。

其他的瀏覽器則是如此解讀:

(1)的「/*」被視為是註解的開始,(2)的「\」被視為在註解中所以沒有作用,(3)的「*/」被視為註解的結束,(4)的「/*」被視為第二個註解的開始,(5)的「@import」被視為註解內,而最後(6)的「*/」為結束註解,於是「@import」不會被執行。

反過來看,我們的過濾器如果需要正確識別「ex/**/pression」以及類似變形,就必須正確判斷註解的開始與結束,可是各家瀏覽器在CSS處理機制實做上的不同,增加了我們過濾器的困難度。

事實上,這些各家瀏覽器在CSS實做上的不同,不但大家早就熟悉,還早就被當成「功能」來使用--利用這些不同來有效判斷不同的瀏覽器,讓不同瀏覽器讀取不同的CSS檔。這種技巧叫做「CSS Hack」或「CSS Filter」,大家可以參考 Wikipedia 中的定義,這邊就不多著墨。

但是由於這些差異被當成「功能」來使用,讓錯的一方也不能修正,因為如果修正了,反而很多網站的顯示就要變得不正確了。於是目前瀏覽器廠商只能推出新一代瀏覽器(IE5 -> IE6 -> IE7)時,才對這些bug進行修正。

為何瀏覽器的lexer/parser或preprocessor這麼糟糕?事實上,很久以前大家不就發現,資料庫的SQL處理器也是一樣糟嗎?「se/**/lect」這種SQL注入(SQL injection)手法大家應該都很常用。事實上,MS SQL伺服器的T-SQL處理器以及MySQL的SQL處理器,在這方面也都是有名的...說複雜好了。

雖然攻擊手法並不新,但是從噗浪的七代實驗可以知道,老人雖然很熟,新人還是會不斷地重複採地雷,正確地過濾惡意字串,並非易事。

瀏覽器/資料庫的lexer/parser/proprocessor處理器的複雜與不穩定,不但增加了資安的過濾函式的難度,也同時增加了WAF(Web應用程式防火牆、web application firewall)在實做與設定上的難度。

這些也就是為何,在情況允許之下,程式中應盡量避免過濾機制,而採用參數化機制。你的過濾機制,真的安全嗎?

作者Wayne為阿碼科技一員
系列第一篇:誰在看我的噗?第一回:DOM沙盒 vs 跨網站腳本漏洞(XSS)
系列第二篇:誰在看我的噗?第二回:IE執行模式 vs 跨網站腳本漏洞(XSS)
系列第三篇:(本篇)誰在看我的噗?第三回:弔詭的過濾函式
系列第四篇:誰在看我的噗?第四回:我噗誰在看!

4 篇回應 :

yv 提到...

為何不實作 white-listed upload filter,
只把認得又充許的 css style 挑出來組成另一個 stylesheet,
再存起來呢?

就不用管各家 browser 的實作不同了?

Kenny Hsu 提到...

這篇攻擊真精彩!

March 提到...

真的是精彩的攻擊, 好好學起來

匿名 提到...

如果用白名單過濾應該比較好吧

張貼留言