庖丁解"D",游刃有余
------Discuz!免費(fèi)版安全性分析
作者:[I.T.S]Jambalaya
論壇:http://www.itaq.org
前言:記得第一次見(jiàn)分析家的時(shí)候,他問(wèn)我最近在讀什么,我告訴他我在繼續(xù)讀雷傲論壇的代碼,他笑著說(shuō)道:"那種東西漏洞一大把,讀他做什么?去讀Discuz!吧,就安全性而言,這個(gè)還有點(diǎn)挑戰(zhàn)性",
庖丁解
。讀了幾天后,覺(jué)得確實(shí)是一塊硬骨頭。后來(lái)事情一多,慢慢的忘卻了。前不久,我的一位朋友和我半開(kāi)玩笑的說(shuō):“如果你能找到Discuz的漏洞,我就請(qǐng)你吃羊棒。找不到你請(qǐng)我吃。”(注:羊棒是我們附近的一家飯店的特色菜),朋友提出要求漏洞必須是在php默認(rèn)magic_qoute_gpc為on的時(shí)候也是可利用的,并且限期一個(gè)星期之內(nèi)。我當(dāng)時(shí)因?yàn)榫瓜胫虬袅,哈喇子嘩啦嘩啦的往下流,于是連想都沒(méi)想就一口答應(yīng)下來(lái)了......
于是故事就這么開(kāi)始了......
一、漏洞涉及版本
Discuz!2.0以下免費(fèi)版本(商業(yè)版沒(méi)有拿到)。1.01及其以下的漏洞利用可能有所不同,但漏洞依然存在。
二、漏洞分析
由于install.php的程序書(shū)寫錯(cuò)誤,導(dǎo)致惡意用戶構(gòu)造語(yǔ)句可以寫入webshell,進(jìn)而控制整個(gè)服務(wù)器。
前幾個(gè)晚上,把前臺(tái)文件,只要是數(shù)據(jù)庫(kù)調(diào)用中的變量都看了一遍?纯词遣皇怯羞^(guò)濾不嚴(yán)的地方,看完后覺(jué)得,過(guò)濾不嚴(yán)的地方的確不少,但是都已經(jīng)被單引號(hào)保護(hù)起來(lái)了。在php中,如果magic_qoute_gpc=on(默認(rèn)的)編譯器會(huì)自動(dòng)把單引號(hào)等特殊字符轉(zhuǎn)義,而這個(gè)時(shí)候我們想改變程序的執(zhí)行流程是非常困難的。這樣大大的增加了入侵的難度,在某種程度上,也的確保證了其安全。這也是為什么朋友提出一定要在magic_qoute_gpc為on的時(shí)候依然可以利用的要求。如果需要單引號(hào)介入才能利用的漏洞,在Discuz!論壇中基本上是沒(méi)什么用處的。
在把所有文件中數(shù)據(jù)庫(kù)連接的地方讀了一個(gè)遍后,沒(méi)找到任何有價(jià)值的東西。這個(gè)時(shí)候思路開(kāi)始有一點(diǎn)亂了,我在猶豫自己是否應(yīng)該試著從程序員的思維邏輯漏洞下手看看,這就意味著我要去通讀所有代碼,找到程序員在寫程序時(shí)考慮不周全的地方,這種漏洞多半是上下文邏輯關(guān)系錯(cuò)誤,或者是限定不唯一,還有就是一些本來(lái)應(yīng)該注意,但管理員卻偏偏忽略的地方。
想到這里,看來(lái)沒(méi)什么能偷懶的了,只能通讀代碼。因?yàn)榧热荒繕?biāo)是邏輯錯(cuò)誤,那么就一定要細(xì)看上下文的關(guān)系,所以選擇從入口點(diǎn)開(kāi)始讀起。入口點(diǎn)就應(yīng)該是logging.php,因?yàn)檫@是登陸的地方,所有人都會(huì)從這里登陸進(jìn)入論壇。上吧!
老規(guī)矩,我們先來(lái)看一段代碼:
=========codzbegin==========
50$errorlog="$username\t".substr($passWord,0,2);
......
54$errorlog.=substr($password,-1)."\t$onlineip\t$timestamp\n";
55$password=md5($password);
56$query=$db->query("SELECTm.usernameasdiscuz_user,m.passwordasdiscuz_pw,m.status,m.styleidASstyleidmem,m.lastvisit,u.groupid,u.isadmin,u.specifiedusersLIKE'%\t$username\t%'ASspecifieduser
FROM$table_membersmLEFTJOIN$table_usergroupsuONu.specifiedusersLIKE'%\t$username\t%'OR(u.status=m.statusAND((u.creditshigher='0'ANDu.creditslower='0'ANDu.specifiedusers='')OR(m.credit>=u.creditshigherANDm.creditWHEREusername='$username'ANDpassword='$password'ORDERBYspecifieduserDESC");
......
69if(!$discuz_user)
{
70fopen($discuz_root.'./forumdata/illegallog.php','a');
71
72
73
74showmessage('login_invalid','index.php');
}
=========codzendz==============
這段代碼我們來(lái)一句一句看看,他先紀(jì)錄輸入的用戶名和密碼,密碼只取前兩位,然后取密碼后一位,并且將ip和時(shí)間一起賦過(guò)來(lái)。然后去密碼的md5值,放到數(shù)據(jù)庫(kù)中去。如果你認(rèn)為我們可以改變數(shù)據(jù)庫(kù)執(zhí)行語(yǔ)句的操作流程那就錯(cuò)了,面對(duì)單引號(hào)我們沒(méi)有什么可以做的(至少是我做不了什么,除非加密)。后面如果用戶名和密碼不對(duì)則將紀(jì)錄下錯(cuò)誤用戶名和密碼到illegallog.php中。整個(gè)這個(gè)記錄錯(cuò)誤密碼的過(guò)程中,變量沒(méi)有經(jīng)過(guò)任何驗(yàn)證,也就是說(shuō)如果我成心輸入錯(cuò)誤的用戶名,他也不會(huì)作檢查然后直接記錄下來(lái)。那么如果我的錯(cuò)誤的用戶名是一個(gè)可執(zhí)行的代碼,他也會(huì)記錄下來(lái)。在他記錄下來(lái)之后我們?nèi)フ{(diào)用這個(gè)文件就可以形成一個(gè)shell。
到這里你是不是已經(jīng)興奮了?對(duì)不起,你的興奮無(wú)效。我是一點(diǎn)都興奮不起來(lái),因?yàn)槲以谇懊孀x第一遍的時(shí)候,特別注意過(guò)Discuz!對(duì)文件句柄的操作,他的確對(duì)個(gè)別變量沒(méi)有過(guò)濾,但是他在install.php中得初始化的時(shí)候,已經(jīng)給所有用到的以.php結(jié)尾的數(shù)據(jù)文件開(kāi)始的地方添加了一句:。這是初始化的時(shí)候?qū)懭氲,我們都?yīng)該明白這句話的作用。你無(wú)法去調(diào)用你寫入的東西,因?yàn)橐婚_(kāi)始就已經(jīng)結(jié)束了。
這樣顯然是無(wú)法成功的。有點(diǎn)煩了,心想不就是5根羊棒么?輸就輸了!一賭氣扔下代碼,自己跑到姥姥的那屋,摟著姥姥撒起嬌來(lái),(在姥姥面前撒嬌、耍賴、搗亂是我最喜歡的事情之一)我在姥姥面前反復(fù)咒罵著Discuz!的變態(tài),說(shuō)我自己如何如何認(rèn)真的讀代碼。姥姥并不知道我在說(shuō)什么,也不在乎我給她搗亂,繼續(xù)看著自己的電視。過(guò)了一會(huì)兒,姥姥應(yīng)了一句:"你這孩子啊,就是粗心,一點(diǎn)都不心細(xì),你看你這什么碼(估計(jì)是說(shuō)代碼)又壞了吧"。我嘎嘎大聲的笑著爬回了自己的屋子。坐在電腦前,喝了杯白開(kāi)水,冷靜了一下。
做安全的人細(xì)心,毅力,自學(xué),善于總結(jié)是非常必要的,
電腦資料
《庖丁解》(http://www.msguai.com);剡^(guò)頭又把logging.php文件重新讀一編,看看有沒(méi)有什么自己忽略的地方。文件并不長(zhǎng),又看了一遍覺(jué)得還是沒(méi)什么問(wèn)題。既然這里沒(méi)什么問(wèn)題,思路就放到那句上,這句話是如何寫入的呢?于是在install.php中翻到了這段代碼:
==========codzbegin==========
29functionloginit($log){
30echo'初始化記錄'.$log;
31$fp=
32\n");
33
34result();
35}
......
1389loginit('karmalog');
1390loginit('illegallog');
1391loginit('modslog');
1392loginit('cplog');
1393dir_clear('./forumdata/templates');
1394dir_clear('./forumdata/cache');
==========codzendz==========
很顯然,loginit這個(gè)函數(shù)就是在往illegallog.php中寫入這句話。這樣我們就算寫入了代碼也會(huì)因?yàn)槟蔷湓挼拇嬖诙鵁o(wú)法調(diào)用執(zhí)行。我們已經(jīng)完全進(jìn)入了一個(gè)死胡同。但我似乎覺(jué)得這段代碼哪里有些問(wèn)題,再仔細(xì)看了一下,那個(gè)fopen看著怎么那么不順眼啊。如果沒(méi)記錯(cuò)的話fopen的語(yǔ)法格式應(yīng)該是這樣的:
resourcefopen(stringfilename,stringmode[,intuse_include_path[,resourcezcontext]])
前面兩個(gè)參數(shù),一個(gè)是文件句柄,或者指定打開(kāi)哪個(gè)文件。第二個(gè)參數(shù)是指定打開(kāi)的方式,比如讀還是寫。這里要提醒大家的是這個(gè)兩個(gè)參數(shù)是必選的。比如我們寫入目錄下的Jambalaya.php,我們的語(yǔ)句是這么寫的:fopen('./Jambalya.php','w'),后面那個(gè)打開(kāi)方式必須選的,否則就會(huì)報(bào)錯(cuò)?墒俏覀冏⒁獾剑拇a中只有一個(gè)參數(shù),這樣程序怎么可能正確執(zhí)行呢?
我們來(lái)做一個(gè)試驗(yàn),創(chuàng)建一個(gè)1.php,寫入如下代碼:
===========codzbegin==========
$fp=
@fwrite($fp, "\n");
@fclose($fp);
echo"sUCcess!";
===========codzendz============
在這里做一個(gè)和Discuz!中的一樣的環(huán)境,如果這個(gè)程序執(zhí)行成功,那么會(huì)在根目錄下產(chǎn)生一個(gè)2.php,而2.php的開(kāi)頭一行應(yīng)該是,并且屏幕上顯示success,其實(shí)這里的success就是用來(lái)讓我們了解程序執(zhí)行所到的位置。我們?cè)趗rl中提交http://127.0.0.1/myhome/1.php,
姥姥教訓(xùn)的沒(méi)錯(cuò),我太粗心了!
假設(shè)一切都是按我的思路走過(guò)的話,也就說(shuō)在安裝初始化的時(shí)候,因?yàn)槟莻(gè)fopen的錯(cuò)誤使用,所以discuz/forumdata目錄下絕對(duì)不會(huì)產(chǎn)生一個(gè)含有代碼的illegallog.php文件,但是因?yàn)橐种瞥鲥e(cuò)信息,所安裝的時(shí)候會(huì)依然顯示初始化成功,其實(shí)卻并沒(méi)有初始化,更沒(méi)有產(chǎn)生illegallog.php。而如果這里沒(méi)有初始化,也就意味著illegallog.php的產(chǎn)生和初始化將在logging.php中完成。logging.php的初始化并沒(méi)有對(duì)文件寫入任何保護(hù)語(yǔ)句或者過(guò)濾措施來(lái)避免用戶調(diào)用。到這里,一切都明朗了。說(shuō)得直白一點(diǎn),也就是因?yàn)閕nstall.php文件錯(cuò)誤的初始化的緣故,我們可以通過(guò)logging.php寫入惡意代碼,然后調(diào)用那個(gè)文件來(lái)產(chǎn)生一個(gè)shell控制整個(gè)網(wǎng)站。
三、利用方法
不用注冊(cè)任何賬戶,到登陸頁(yè)面,在登陸用戶名的地方先輸入123456,回車。這里大家可能明白了,密碼前兩位是顯示的,這樣illegallog.php里面保存的就是:
*****6127.0.0.11022383175
這就可以查看php的設(shè)置了,我們先看一下register_globals這個(gè)設(shè)置是否為on。(大部分的網(wǎng)站都是on)好,然后我們?cè)诘顷懣谳斎?這里最好不用system(),我在做測(cè)試的時(shí)候很多網(wǎng)站把這個(gè)system()函數(shù)禁用了。
然后我們調(diào)http://192.168.0.13/forumdata/illegallog.php?cmd=dir
前面出來(lái)一堆垃圾信息,往最下面看是不是可以看到目錄被列出來(lái)了?但是這樣寫入有點(diǎn)麻煩,因?yàn)樵趯?shí)驗(yàn)的時(shí)候,大的網(wǎng)站注冊(cè)用戶有10萬(wàn)多人,那么這個(gè)文件會(huì)大的出奇,打開(kāi)速度奇慢。
那么我們這里其實(shí)還可以這樣寫入,我們把php的shell改成jpg格式的圖片,上傳到主機(jī)。
在URL中調(diào)用:
http://192.168.0.13/forumdata/illegallog.p...chments/Jam.php
然后我們直接提交URL:
http://192.168.0.13/attachments/Jam.php
也許有人問(wèn)我,為什么不在用戶名處直接輸入一個(gè)shell,為什么要分開(kāi)在用戶名和密碼出輸入“”.其實(shí)這個(gè)我也想過(guò),而且我直接在用戶名處輸入在本機(jī)測(cè)試也成功了,但是在其他的網(wǎng)站上測(cè)試的時(shí)候,卻發(fā)現(xiàn)只要輸入就會(huì)被過(guò)濾掉,我不知道是不是版本的問(wèn)題。(本來(lái)想把別人的代碼拉下來(lái)比對(duì)一下的,后來(lái)一犯懶就沒(méi)弄,大家有興趣可以自行測(cè)試)
那么如果register_globals如果為off是不是就不可以了呢?答案是否定的,這里給大家一點(diǎn)思考的空間。自己想吧,嘿嘿。給大家一點(diǎn)提示:我們不一定要一次寫入,通過(guò)構(gòu)造自己的語(yǔ)句來(lái)繞開(kāi)限制,實(shí)現(xiàn)別的功能進(jìn)而寫入一個(gè)完整的shell。
這里順便說(shuō)一下1.01版以下的免費(fèi)版本,這個(gè)漏洞的利用會(huì)有所不同,所謂的不同不過(guò)就是文件名的變化,這里也不做詳解了,大家自己試驗(yàn)吧。畢竟,你只有自己親自動(dòng)手后才能學(xué)到更多的東西。
我們這里看見(jiàn)任何細(xì)節(jié)的不注意,都會(huì)導(dǎo)致你固若金湯的防線土崩瓦解!扒Ю镏,潰于蟻穴”并非虛言啊!
整篇文章到這里就結(jié)束了。這里要補(bǔ)充一些東西,剛開(kāi)始的時(shí)候,發(fā)現(xiàn)這個(gè)漏洞只是想放到itaq.org的隱藏版給IT安全的兄弟們玩得,沒(méi)想過(guò)公布。倒不是因?yàn)槲倚猓饕亲约涸跍y(cè)試的時(shí)候發(fā)現(xiàn)很多商業(yè)版本的論壇,這個(gè)漏洞無(wú)法利用,都正確的初始化了,我一度認(rèn)為是自己拿到的版本有問(wèn)題,就沒(méi)打算寫出來(lái)。直到后來(lái),一些 圈里的朋友讓我?guī)兔y(cè)試幾個(gè)以Discuz!做論壇的網(wǎng)站時(shí),他們告訴我他們的Discuz!論壇是從網(wǎng)上下載的免費(fèi)版本后,我用這個(gè)漏洞輕松的進(jìn)去并拿到了權(quán)限。之后,我對(duì)一大批免費(fèi)版本的Discuz!網(wǎng)站做測(cè)試,才發(fā)現(xiàn)可以用這個(gè)漏洞對(duì)免費(fèi)版的Discuz!進(jìn)行攻擊都能成功,而網(wǎng)上免費(fèi)版的使用用戶也不算少數(shù)。而且一旦成功就是一個(gè)可以進(jìn)而控制整個(gè)服務(wù)器的漏洞。美中不足的就是只能攻擊免費(fèi)版。于是決定寫出來(lái)給大家探討一下。
行文倉(cāng)促,技術(shù)有限,文章如果有誤,望高手來(lái)http://www.iraq.org