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

標題: ThinkPHP 5.0 遠程代碼執(zhí)行漏洞分析 [打印本頁]

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


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


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

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


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


thinkphp支持配置“表單偽裝變量”,默認情況下該變量值為_method


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


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


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

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


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


當傳入的$method === true時 會執(zhí)行如下代碼:


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


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


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


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

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

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

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

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

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

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

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

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

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

(, 下載次數(shù): 67)
然后會進入
(, 下載次數(shù): 67)

最后返回$result. 因此最終$dispatch值為:
(, 下載次數(shù): 65)
因而在傳入exec函數(shù)后,觸發(fā)Request類的param方法,最終覆蓋Request類的server變量,接著通過Request類的input方法,實現(xiàn)任意代碼執(zhí)行:
(, 下載次數(shù): 80)
0x05 本地復現(xiàn)
環(huán)境獲。
https://github.com/vulhub/vulhub/tree/master/thinkphp/5.0.23-rce
測試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ù): 76)






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