ThinkPHP 5.0 遠(yuǎn)程代碼執(zhí)行漏洞分析
0x01 前言
本文主要以官網(wǎng)下載的5.0.23 完整版(thinkphp_5.0.23_with_extend.zip)為例分析。
0x02 漏洞分析(一)
Thinkphp處理請求的關(guān)鍵類為Request(thinkphp/library/think/Request.php)
該類可以實(shí)現(xiàn)對HTTP請求的一些設(shè)置,其中成員函數(shù)method用來獲取當(dāng)前請求類型,其定義如下:
t01.png (76.01 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:39 上傳
該函數(shù)主要在其他成員函數(shù)(例如isGet、isPost、isPut等)中被用來做請求類型判斷
t02.png (45.31 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:39 上傳
thinkphp支持配置“表單偽裝變量”,默認(rèn)情況下該變量值為_method
t03.png (51.13 KB, 下載次數(shù): 81)
下載附件
保存到相冊
2019-5-20 14:40 上傳
因此在method()中,可以通過“表單偽裝變量”進(jìn)行變量覆蓋實(shí)現(xiàn)對該類任意函數(shù)的調(diào)用,并且$_POST作為函數(shù)的參數(shù)傳入
t04.png (65.42 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:40 上傳
Request類的構(gòu)造函數(shù)定義如下:
t05.png (38.93 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:40 上傳
構(gòu)造函數(shù)中,主要對$option數(shù)組進(jìn)行遍歷,當(dāng)$option的鍵名為該類屬性時,則將該類同名的屬性賦值為$options中該鍵的對應(yīng)值,因此可以構(gòu)造請求如下,來實(shí)現(xiàn)對Request類屬性值的覆蓋,例如覆蓋filter屬性。filter屬性保存了用于全局過濾的函數(shù),因此在thinkphp 5.0.10 中可以通過構(gòu)造如下請求實(shí)現(xiàn)代碼執(zhí)行:
t06.png (42.24 KB, 下載次數(shù): 79)
下載附件
保存到相冊
2019-5-20 14:41 上傳
0x03 漏洞分析(二)在官網(wǎng)最新下載的5.0.23完整版中,在App類(thinkphp/library/think/App.php)中module方法增加了設(shè)置filter參數(shù)值的代碼,用于初始化filter。因此通過上述請求設(shè)置的filter參數(shù)值會被重新覆蓋為空導(dǎo)致無法利用:
t07.png (15.48 KB, 下載次數(shù): 67)
下載附件
保存到相冊
2019-5-20 14:42 上傳
在5.0.23的Request類中,存在param成員函數(shù)用于獲取當(dāng)前請求的參數(shù)。其中也有調(diào)用method()函數(shù),并且傳入?yún)?shù)值為true。param函數(shù)代碼實(shí)現(xiàn)如下:
t08.png (122.66 KB, 下載次數(shù): 87)
下載附件
保存到相冊
2019-5-20 14:42 上傳
當(dāng)傳入的$method === true時 會執(zhí)行如下代碼:
t09.png (76.42 KB, 下載次數(shù): 66)
下載附件
保存到相冊
2019-5-20 14:43 上傳
其中server()實(shí)現(xiàn)如下:
t10.png (45.16 KB, 下載次數(shù): 70)
下載附件
保存到相冊
2019-5-20 14:43 上傳
由此可見,參數(shù)$name為REQUEST_METHOD,最終會調(diào)用input函數(shù)。input函數(shù)實(shí)現(xiàn)如下。最終會執(zhí)行到$filter = $this->getFilter($filter,$default); 來解析過濾器。
t11.png (82.28 KB, 下載次數(shù): 65)
下載附件
保存到相冊
2019-5-20 14:43 上傳
此時$filter 為‘’,$default為null,繼續(xù)跟進(jìn)查看getFilter函數(shù)的實(shí)現(xiàn):
t12.png (35.87 KB, 下載次數(shù): 70)
下載附件
保存到相冊
2019-5-20 14:44 上傳
由于$filter = ‘‘,因此可以執(zhí)行到紅線處,并且最終$filter會被賦值進(jìn)$this->filter ,和$default 即 null。因此經(jīng)過“解析過濾器”的過程,$filter最終可被添加來自請求中的filter。之后,代碼判斷了$data是否為數(shù)組,最終將每個值作為參數(shù),通過filterValue()函數(shù)進(jìn)行過濾。而$data即為server()函數(shù)中傳入的$this->server ,因此也可通過在調(diào)用構(gòu)造函數(shù)時,實(shí)現(xiàn)對$this->server值的覆蓋。 filterValue函數(shù)實(shí)現(xiàn)如下:
t13.png (83.49 KB, 下載次數(shù): 76)
下載附件
保存到相冊
2019-5-20 14:44 上傳
最終會通過call_user_func來實(shí)現(xiàn)代碼執(zhí)行。此處原理同剛剛爆出的THINKPHP 5 通過控制controller值實(shí)現(xiàn)反射調(diào)用指定類來遠(yuǎn)程代碼執(zhí)行漏洞原理一致,不再過多分析。 因此只需要找到自動觸發(fā)調(diào)用param()函數(shù)的地方即可。 其中在App類中的run()函數(shù)中,如果開啟了debug模式,會實(shí)現(xiàn)日志的記錄,其中有調(diào)用$request->param()。
t14.png (85.34 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:45 上傳
因此在debug模式下,發(fā)送如下請求可實(shí)現(xiàn)代碼執(zhí)行:
t15.png (56.33 KB, 下載次數(shù): 71)
下載附件
保存到相冊
2019-5-20 14:45 上傳
例如touch /tmp/xxxx
t16.png (34.17 KB, 下載次數(shù): 71)
下載附件
保存到相冊
2019-5-20 14:45 上傳
0x04 漏洞分析(三)繼續(xù)分析,可以看到“記錄路由和請求信息”之前,還實(shí)現(xiàn)了“未設(shè)置調(diào)度信息則進(jìn)行URL路由檢測!逼渲型ㄟ^routeCheck函數(shù)來設(shè)置$dispatch。最終再通過exec函數(shù),將$request和$config 作為參數(shù)傳入后執(zhí)行。其中$config 是通過initCommon函數(shù)調(diào)用init函數(shù)來加載config配置文件來實(shí)現(xiàn)賦值。$request即為Request::instance()。
t17.png (88.32 KB, 下載次數(shù): 70)
下載附件
保存到相冊
2019-5-20 14:46 上傳
首先會通過加載config文件導(dǎo)入“路由配置”。默認(rèn)配置如下:
t18.png (28.13 KB, 下載次數(shù): 61)
下載附件
保存到相冊
2019-5-20 14:46 上傳
然后通過Route類的import方法加載路由。由于在入口文件index.php(public/index.php)中加載了start.php(thinkphp/start.php)
t19.png (28.2 KB, 下載次數(shù): 73)
下載附件
保存到相冊
2019-5-20 14:46 上傳
start.php又加載了base.php(thinkphp/base.php)
t20.png (24.69 KB, 下載次數(shù): 73)
下載附件
保存到相冊
2019-5-20 14:47 上傳
base.php中實(shí)現(xiàn)了注冊自動加載
tx.png (20.22 KB, 下載次數(shù): 67)
下載附件
保存到相冊
2019-5-20 14:49 上傳
register函數(shù)實(shí)現(xiàn)如下:
t21.png (81.62 KB, 下載次數(shù): 77)
下載附件
保存到相冊
2019-5-20 14:47 上傳
該函數(shù)完成了對VENDOR下class的加載。其中完成了驗證碼類路由的加載:
t22.png (51.87 KB, 下載次數(shù): 64)
下載附件
保存到相冊
2019-5-20 14:50 上傳
因此上面import之后,Route類$rules值將形如下:
t23.png (23.05 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:50 上傳
然后代碼會執(zhí)行到“路由檢測”的部分即調(diào)用Route::check的地方,路由檢測主要用于根據(jù)路由的定義返回不同的URL調(diào)度。Route類check函數(shù)實(shí)現(xiàn)如下:
t24.png (99 KB, 下載次數(shù): 75)
下載附件
保存到相冊
2019-5-20 14:50 上傳
可以看到該check函數(shù)中調(diào)用了Request類的method方法。并將函數(shù)執(zhí)行結(jié)果轉(zhuǎn)換為小寫后保存在$method變量。由于Request類中method函數(shù)在返回method時直接判斷了如果存在$this->method直接返回:
t25.png (63.79 KB, 下載次數(shù): 63)
下載附件
保存到相冊
2019-5-20 14:51 上傳
因此我們在調(diào)用構(gòu)造函數(shù)覆蓋變量時,可以直接覆蓋method,這樣上面的$method = strtolower($request->method()); 的$method最終的值就可以被控制了。 回到check函數(shù),在設(shè)置完$method 后,會根據(jù)$method在self:rules中獲取對應(yīng)$method的路由信息,并將結(jié)果賦值給$rules.
t26.png (91.38 KB, 下載次數(shù): 60)
下載附件
保存到相冊
2019-5-20 14:51 上傳
最終函數(shù)的return將依賴$rules ,因此$method不同返回也不同。當(dāng)$method 為get 時,$rules 將為如下:
t27.png (31.51 KB, 下載次數(shù): 70)
下載附件
保存到相冊
2019-5-20 14:53 上傳
由于$item = str_replace(‘|’, ‘/‘, $url); 而 $url = str_replace($depr, ‘|’, $url),而這個$url最初為該函數(shù)的參數(shù),在App類routeCheck方法中調(diào)用
t28.png (92.12 KB, 下載次數(shù): 69)
下載附件
保存到相冊
2019-5-20 14:53 上傳
而這個$path = $request->path(),Request類的path方法實(shí)現(xiàn)如下:
t29.png (55.36 KB, 下載次數(shù): 65)
下載附件
保存到相冊
2019-5-20 14:54 上傳
當(dāng)is_null($this->path)時,通過pathinfo()函數(shù)來獲取
t30.png (92.11 KB, 下載次數(shù): 64)
下載附件
保存到相冊
2019-5-20 14:55 上傳
其中var_pathino 默認(rèn)值為s
t31.png (38.24 KB, 下載次數(shù): 91)
下載附件
保存到相冊
2019-5-20 14:55 上傳
因此綜上,我們可以通過URL中s參數(shù)來設(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)用
t32.png (27.86 KB, 下載次數(shù): 66)
下載附件
保存到相冊
2019-5-20 14:56 上傳
其中exec的實(shí)現(xiàn)如下
t33.png (106.92 KB, 下載次數(shù): 65)
下載附件
保存到相冊
2019-5-20 14:56 上傳
exec會根據(jù)$dispatch[‘type’]來決定實(shí)際執(zhí)行的代碼。
前面分析可知,我們需要觸發(fā)Request類中param函數(shù)的調(diào)用來完成對filter的覆蓋。此處顯而易見的是,當(dāng)$dispatch[‘type’]為controller 和method時有直接的調(diào)用。當(dāng)然其他的類型,例如當(dāng)$dispatch[‘type’]為function,調(diào)用了invokeFunction,而invokeFunction調(diào)用了bindParams函數(shù),bindParams函數(shù)里存在對param函數(shù)的調(diào)用?傊覀冃枰刂普埱髐rl中s的值完成設(shè)置不同的$method,最終讓routeCheck返回我們需要的$dispath即可。 例如我們控制url為 /public/index.php?s=captcha,同時post body為_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al則check函數(shù)最終會進(jìn)入
t34.png (12.21 KB, 下載次數(shù): 74)
下載附件
保存到相冊
2019-5-20 14:57 上傳
然后checkRoute函數(shù),由于rule為字符串,
t35.png (9.87 KB, 下載次數(shù): 73)
下載附件
保存到相冊
2019-5-20 14:57 上傳
因此最終會進(jìn)入
t36.png (107.52 KB, 下載次數(shù): 74)
下載附件
保存到相冊
2019-5-20 14:57 上傳
然后會進(jìn)入 parseRule
t37.png (120 KB, 下載次數(shù): 67)
下載附件
保存到相冊
2019-5-20 14:57 上傳
然后會進(jìn)入
t38.png (130.56 KB, 下載次數(shù): 67)
下載附件
保存到相冊
2019-5-20 14:58 上傳
最后返回$result. 因此最終$dispatch值為:
t39.png (9.79 KB, 下載次數(shù): 64)
下載附件
保存到相冊
2019-5-20 14:58 上傳
因而在傳入exec函數(shù)后,觸發(fā)Request類的param方法,最終覆蓋Request類的server變量,接著通過Request類的input方法,實(shí)現(xiàn)任意代碼執(zhí)行:
t40.png (85.57 KB, 下載次數(shù): 80)
下載附件
保存到相冊
2019-5-20 14:59 上傳
0x05 本地復(fù)現(xiàn)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:
t41.png (33.64 KB, 下載次數(shù): 75)
下載附件
保存到相冊
2019-5-20 15:00 上傳
|