資源共享吧|易語(yǔ)言論壇|逆向破解教程|輔助開(kāi)發(fā)教程|網(wǎng)絡(luò)安全教程|www.rigasin.com|我的開(kāi)發(fā)技術(shù)隨記

標(biāo)題: ThinkPHP 5.0 遠(yuǎn)程代碼執(zhí)行漏洞分析 [打印本頁(yè)]

作者: 1366875557    時(shí)間: 2019-5-20 15:00
標(biāo)題: ThinkPHP 5.0 遠(yuǎn)程代碼執(zhí)行漏洞分析
ThinkPHP 5.0 遠(yuǎn)程代碼執(zhí)行漏洞分析


0x01 前言
本文主要以官網(wǎng)下載的5.0.23 完整版(thinkphp_5.0.23_with_extend.zip)為例分析。
0x02 漏洞分析(一)
Thinkphp處理請(qǐng)求的關(guān)鍵類(lèi)為Request(thinkphp/library/think/Request.php)
該類(lèi)可以實(shí)現(xiàn)對(duì)HTTP請(qǐng)求的一些設(shè)置,其中成員函數(shù)method用來(lái)獲取當(dāng)前請(qǐng)求類(lèi)型,其定義如下:


(, 下載次數(shù): 101)

該函數(shù)主要在其他成員函數(shù)(例如isGet、isPost、isPut等)中被用來(lái)做請(qǐng)求類(lèi)型判斷


(, 下載次數(shù): 101)


thinkphp支持配置“表單偽裝變量”,默認(rèn)情況下該變量值為_(kāi)method


(, 下載次數(shù): 111)


因此在method()中,可以通過(guò)“表單偽裝變量”進(jìn)行變量覆蓋實(shí)現(xiàn)對(duì)該類(lèi)任意函數(shù)的調(diào)用,并且$_POST作為函數(shù)的參數(shù)傳入


(, 下載次數(shù): 103)
Request類(lèi)的構(gòu)造函數(shù)定義如下:
(, 下載次數(shù): 100)
構(gòu)造函數(shù)中,主要對(duì)$option數(shù)組進(jìn)行遍歷,當(dāng)$option的鍵名為該類(lèi)屬性時(shí),則將該類(lèi)同名的屬性賦值為$options中該鍵的對(duì)應(yīng)值,因此可以構(gòu)造請(qǐng)求如下,來(lái)實(shí)現(xiàn)對(duì)Request類(lèi)屬性值的覆蓋,例如覆蓋filter屬性。filter屬性保存了用于全局過(guò)濾的函數(shù),因此在thinkphp 5.0.10 中可以通過(guò)構(gòu)造如下請(qǐng)求實(shí)現(xiàn)代碼執(zhí)行:
(, 下載次數(shù): 115)
0x03 漏洞分析(二)
在官網(wǎng)最新下載的5.0.23完整版中,在App類(lèi)(thinkphp/library/think/App.php)中module方法增加了設(shè)置filter參數(shù)值的代碼,用于初始化filter。因此通過(guò)上述請(qǐng)求設(shè)置的filter參數(shù)值會(huì)被重新覆蓋為空導(dǎo)致無(wú)法利用:

(, 下載次數(shù): 101)
在5.0.23的Request類(lèi)中,存在param成員函數(shù)用于獲取當(dāng)前請(qǐng)求的參數(shù)。其中也有調(diào)用method()函數(shù),并且傳入?yún)?shù)值為true。param函數(shù)代碼實(shí)現(xiàn)如下:


(, 下載次數(shù): 119)


當(dāng)傳入的$method === true時(shí) 會(huì)執(zhí)行如下代碼:


(, 下載次數(shù): 99)


其中server()實(shí)現(xiàn)如下:
(, 下載次數(shù): 103)


由此可見(jiàn),參數(shù)$name為REQUEST_METHOD,最終會(huì)調(diào)用input函數(shù)。input函數(shù)實(shí)現(xiàn)如下。最終會(huì)執(zhí)行到$filter = $this->getFilter($filter,$default); 來(lái)解析過(guò)濾器。
(, 下載次數(shù): 98)
此時(shí)$filter 為‘’,$default為null,繼續(xù)跟進(jìn)查看getFilter函數(shù)的實(shí)現(xiàn):
(, 下載次數(shù): 104)


由于$filter = ‘‘,因此可以執(zhí)行到紅線處,并且最終$filter會(huì)被賦值進(jìn)$this->filter ,和$default 即 null。因此經(jīng)過(guò)“解析過(guò)濾器”的過(guò)程,$filter最終可被添加來(lái)自請(qǐng)求中的filter。之后,代碼判斷了$data是否為數(shù)組,最終將每個(gè)值作為參數(shù),通過(guò)filterValue()函數(shù)進(jìn)行過(guò)濾。而$data即為server()函數(shù)中傳入的$this->server ,因此也可通過(guò)在調(diào)用構(gòu)造函數(shù)時(shí),實(shí)現(xiàn)對(duì)$this->server值的覆蓋。
filterValue函數(shù)實(shí)現(xiàn)如下:

(, 下載次數(shù): 108)

最終會(huì)通過(guò)call_user_func來(lái)實(shí)現(xiàn)代碼執(zhí)行。此處原理同剛剛爆出的THINKPHP 5 通過(guò)控制controller值實(shí)現(xiàn)反射調(diào)用指定類(lèi)來(lái)遠(yuǎn)程代碼執(zhí)行漏洞原理一致,不再過(guò)多分析。
因此只需要找到自動(dòng)觸發(fā)調(diào)用param()函數(shù)的地方即可。
其中在App類(lèi)中的run()函數(shù)中,如果開(kāi)啟了debug模式,會(huì)實(shí)現(xiàn)日志的記錄,其中有調(diào)用$request->param()。

(, 下載次數(shù): 104)

因此在debug模式下,發(fā)送如下請(qǐng)求可實(shí)現(xiàn)代碼執(zhí)行:
(, 下載次數(shù): 102)

例如touch /tmp/xxxx
(, 下載次數(shù): 104)

0x04 漏洞分析(三)
繼續(xù)分析,可以看到“記錄路由和請(qǐng)求信息”之前,還實(shí)現(xiàn)了“未設(shè)置調(diào)度信息則進(jìn)行URL路由檢測(cè)�!逼渲型ㄟ^(guò)routeCheck函數(shù)來(lái)設(shè)置$dispatch。最終再通過(guò)exec函數(shù),將$request和$config 作為參數(shù)傳入后執(zhí)行。其中$config 是通過(guò)initCommon函數(shù)調(diào)用init函數(shù)來(lái)加載config配置文件來(lái)實(shí)現(xiàn)賦值。$request即為Request::instance()。
(, 下載次數(shù): 104)

首先會(huì)通過(guò)加載config文件導(dǎo)入“路由配置”。默認(rèn)配置如下:
(, 下載次數(shù): 96)
然后通過(guò)Route類(lèi)的import方法加載路由。由于在入口文件index.php(public/index.php)中加載了start.php(thinkphp/start.php)
(, 下載次數(shù): 105)
start.php又加載了base.php(thinkphp/base.php)
(, 下載次數(shù): 107)
base.php中實(shí)現(xiàn)了注冊(cè)自動(dòng)加載
(, 下載次數(shù): 101)
register函數(shù)實(shí)現(xiàn)如下:
(, 下載次數(shù): 112)
該函數(shù)完成了對(duì)VENDOR下class的加載。其中完成了驗(yàn)證碼類(lèi)路由的加載:
(, 下載次數(shù): 98)
因此上面import之后,Route類(lèi)$rules值將形如下:
(, 下載次數(shù): 100)
然后代碼會(huì)執(zhí)行到“路由檢測(cè)”的部分即調(diào)用Route::check的地方,路由檢測(cè)主要用于根據(jù)路由的定義返回不同的URL調(diào)度。Route類(lèi)check函數(shù)實(shí)現(xiàn)如下:
(, 下載次數(shù): 106)
可以看到該check函數(shù)中調(diào)用了Request類(lèi)的method方法。并將函數(shù)執(zhí)行結(jié)果轉(zhuǎn)換為小寫(xiě)后保存在$method變量。由于Request類(lèi)中method函數(shù)在返回method時(shí)直接判斷了如果存在$this->method直接返回:
(, 下載次數(shù): 98)
因此我們?cè)谡{(diào)用構(gòu)造函數(shù)覆蓋變量時(shí),可以直接覆蓋method,這樣上面的$method = strtolower($request->method()); 的$method最終的值就可以被控制了。
回到check函數(shù),在設(shè)置完$method 后,會(huì)根據(jù)$method在self:rules中獲取對(duì)應(yīng)$method的路由信息,并將結(jié)果賦值給$rules.
(, 下載次數(shù): 96)
最終函數(shù)的return將依賴$rules ,因此$method不同返回也不同。當(dāng)$method 為get 時(shí),$rules 將為如下:
(, 下載次數(shù): 103)
由于$item = str_replace(‘|’, ‘/‘, $url); 而 $url = str_replace($depr, ‘|’, $url),而這個(gè)$url最初為該函數(shù)的參數(shù),在App類(lèi)routeCheck方法中調(diào)用
(, 下載次數(shù): 105)

而這個(gè)$path = $request->path(),Request類(lèi)的path方法實(shí)現(xiàn)如下:
(, 下載次數(shù): 96)

當(dāng)is_null($this->path)時(shí),通過(guò)pathinfo()函數(shù)來(lái)獲取
(, 下載次數(shù): 99)
其中var_pathino 默認(rèn)值為s
(, 下載次數(shù): 123)
因此綜上,我們可以通過(guò)URL中s參數(shù)來(lái)設(shè)置App類(lèi)routeCheck函數(shù)中的$path ,即Router類(lèi)check函數(shù)中的$url ,最終即能控制 Router類(lèi)check函數(shù)中的$item 從而控制check函數(shù)真正return哪種結(jié)果,從而最終影響App類(lèi)run方法的$dispath值即調(diào)度信息。
由于$dispatch 會(huì)作為參數(shù)在exec中調(diào)用
(, 下載次數(shù): 99)
其中exec的實(shí)現(xiàn)如下
(, 下載次數(shù): 100)
exec會(huì)根據(jù)$dispatch[‘type’]來(lái)決定實(shí)際執(zhí)行的代碼。
前面分析可知,我們需要觸發(fā)Request類(lèi)中param函數(shù)的調(diào)用來(lái)完成對(duì)filter的覆蓋。此處顯而易見(jiàn)的是,當(dāng)$dispatch[‘type’]為controller 和method時(shí)有直接的調(diào)用。當(dāng)然其他的類(lèi)型,例如當(dāng)$dispatch[‘type’]為function,調(diào)用了invokeFunction,而invokeFunction調(diào)用了bindParams函數(shù),bindParams函數(shù)里存在對(duì)param函數(shù)的調(diào)用�?傊覀冃枰刂普�(qǐng)求url中s的值完成設(shè)置不同的$method,最終讓routeCheck返回我們需要的$dispath即可。
例如我們控制url為 /public/index.php?s=captcha,同時(shí)post body為_(kāi)method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al則check函數(shù)最終會(huì)進(jìn)入
(, 下載次數(shù): 104)
然后checkRoute函數(shù),由于rule為字符串,
(, 下載次數(shù): 108)
因此最終會(huì)進(jìn)入
(, 下載次數(shù): 108)
然后會(huì)進(jìn)入 parseRule

(, 下載次數(shù): 101)
然后會(huì)進(jìn)入
(, 下載次數(shù): 99)

最后返回$result. 因此最終$dispatch值為:
(, 下載次數(shù): 99)
因而在傳入exec函數(shù)后,觸發(fā)Request類(lèi)的param方法,最終覆蓋Request類(lèi)的server變量,接著通過(guò)Request類(lèi)的input方法,實(shí)現(xiàn)任意代碼執(zhí)行:
(, 下載次數(shù): 115)
0x05 本地復(fù)現(xiàn)
環(huán)境獲�。�
https://github.com/vulhub/vulhub/tree/master/thinkphp/5.0.23-rce
測(cè)試payload:
POST /index.php?s=captcha HTTP/1.1
Host: 192.168.228.140:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 72
Connection: keep-alive
Upgrade-Insecure-Requests: 1

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id
成功執(zhí)行命令id:
(, 下載次數(shù): 110)






歡迎光臨 資源共享吧|易語(yǔ)言論壇|逆向破解教程|輔助開(kāi)發(fā)教程|網(wǎng)絡(luò)安全教程|www.rigasin.com|我的開(kāi)發(fā)技術(shù)隨記 (http://www.rigasin.com/) Powered by Discuz! X3.4