Thinkcmf任意漏洞包含漏洞分析復現:美滿夫妻生活

時間:2023-12-15 09:34:10 作者:美滿夫妻生活 熱度:美滿夫妻生活
美滿夫妻生活描述::Thinkcmf任意漏洞包含漏洞分析復現 簡介 ThinkCMF是一款基于PHP+MYSQL開發的中文內容管理框架,底層采用ThinkPHP3.2.3構建。ThinkCMF提出靈活的應用機制,框架自身提供基礎的管理功能,而開發者可以根據自身的需求以應用的形式進行擴展。每個應用都能獨立的完成自己的任務,也可通過系統調用其他應用進行協同工作。在這種運行機制下,開發商場應用的用戶無需關心開發SNS應用時如何工作的,但他們之間又可通過系統本身進行協調,大大的降低了開發成本和溝通成本。 影響版本 ThinkCMF X1.6.0 ThinkCMF X2.1.0 ThinkCMF X2.2.0 ThinkCMF X2.2.1 ThinkCMF X2.2.2 ThinkCMF X2.2.3 復現環境 我這里下載的2.2.0版本,下載地址為:thinkcmfx2.2.0 安裝過程就略過了 漏洞復現 0×01 payload: http://localhost/thinkcmfx220/?a=display&templateFile=README.md 0×02 payload:?a=fetch&templateFile=public/index&prefix=''&content=file_put_contents('test.php','') 上述請求發送后,會在thinkcmfx根目錄生成test.php,我們訪問一下: 0×03 payload:?a=fetch&content= 這種方式其實利用和pyload2一樣,只不過是直接執行系統命令,我們可以用dnslog的方式檢驗結果,如下 說明命令成功執行 漏洞分析 漏洞分析我可能不會把每行代碼的意思講清楚,但是我會分享一些我在分析這個漏洞時使用的一些小方法 審計mvc架構的應用,第一步就是找到入口,然后順著入口文件,跟著程序邏輯讀下去,直到了解程序大體運作流程,知道基本路由規則(mvc架構的審計工作主要是集中在控制器)。前面的審計開始的前置工作我就不細說了,而且在分析一個漏洞的時候這些前置工作也不一定是必須的,如果你在知道一些信息的情況下,例如,你根據漏洞披露的一些信息已經知道哪個文件有問題了,就不需要再去研究路由了,我這次的分析就是在已知一些條件的情況下進行的,所以我就沒有仔細去讀路由規則,所以,你也可以看到我后面的分析很多都采用的是猜測以及全局搜索這種方式來確定利用點,當然我后面也大概看了下路由,大概跟到App::exec方法里,就可以看到路由規則了,如下: 前面說了那么多廢話…..首先我們看下入口文件index.php確定應用目錄 我們到應用目錄application里的controller看一下,根據路由或已知信息可以確定index.php的請求會被路由到indexcontroller.class.php的index方法 這個方法也沒啥,就是調用了個display顯示了首頁的內容。這些都不是問題的關鍵,關鍵的是thinkcmf是給予tinkphp再開發的,他有一些tp的特性,例如可以通過g\m\a參數指定分組\控制器\方法,這里可以通過a參數直接調用Portal\IndexController父類(HomebaseController)中的一些權限為public的方法。我們自己自己在HomebaseController類中創建一個public屬性的方法 public function test1{ echo 'hello axin'; die; } 然后訪問http://localhost/thinkcmfx220/index.php?a=test1,結果如下 說明確實是可以訪問到public屬性的函數的,此次漏洞主要是利用HomebaseController的display以及fetch方法,因為pyaload已經公開,那么我們就拿payload3:?a=fetch&content=進行分析,看一下fetch方法,如下: payload中只是傳了一個content參數,那么此時的$templateFile值為空,$content值為php代碼,繼續跟進父類的fetch方法,這里的父類跟蹤直接跟到了Controller.class.php中 可見這里執行$this->view->fetch,我們繼續跟進,這里的view就是View.class.php中的類的實例 我們主要關注的點是content變量,上面的代碼有兩個if…else語句,第一個很簡單content不為空,所以執行else分支,第二個我們不能一眼判斷出來,但是這里我們為了效率也就不去深究代碼細節,我們只需要知道后面這個if….else語句到底是進入了哪個分支,所以,我們采用打印變量的方式,類似下面這樣 if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板 echo 33333333; $_content = $content; // 模板陣列變量分解成為獨立變量 extract($this->tVar, EXTR_OVERWRITE); // 直接載入PHP模板 empty($_content)?include $templateFile:eval('?>'.$_content); }else{ echo 444444444; // 視圖解析標簽 $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix); Hook::listen('view_parse',$params); } 這樣當我們訪問頁面的時候,如果頁面出現33333333,則表示進入了第一個分支,否則進入了第二個分支,但是如果跟著我的思路復現了的朋友可能會發現頁面沒有任何回顯,這是因為這段代碼前后分別調用了ob_start與ob_get_clean 這兩個函數的配合會把我們的輸出全部賦值給了$content變量,并不會直接輸出到瀏覽器。所以,我們在分析的時候可以先注釋掉這幾句代碼。然后根據頁面輸出我們就可以確定此處進入了else分支,分支里主要是執行了Hook::listen函數,這個函數是tp里經常見的,以前我也不知道是干嘛的,這次我專門查了一下資料,這個Hook::listen函數就相當于是調用了一個提前注冊好的類中的函數,函數默認是run函數,那么具體調用的是哪個類的run函數呢,這個就取決于傳入的參數了,第一個參數是一個tag,這個tag是與一個類提前綁定的,第二個參數就是要傳入run函數的參數啦。那么這個tag又是在哪里綁定到哪個類的呢?具體在哪個文件定義了映射我也不太清楚,所以,我直接采用全局搜索(phpstorm快捷鍵ctrl+shift+f)view_parse這個tag的方式,來尋找view_parse到底代表哪個類 可以看到整個項目中出現view_parse的文件不多,最后我們確定到common.php,并在其中找到了view_parse對應的類就是Behavior\ParseTemplateBehavior 既然都找到類了,那么就跟進去看一下啦,跟進發現里面確實有一個run函數,確定是他沒錯了 tips:這里跟蹤文件也有個技巧,有時候在定位某個類位于哪個文件時,我們也可以采用全局搜索的方式,或者直接用類名搜索文件名(phpstorm快捷鍵,快速按兩次shift) 又有if分支,為了效率我們同樣可以用剛剛說的方法,判斷到底進入了哪個分支,可以注意到我在上面打了很多斷點,這個斷點是為了標示出哪些行是我自己添加的,或者標示一些重要的邏輯處,方便我后面審計結束刪除自己添加的代碼,也可以防止中途離開再回來看代碼遺忘重點這種情況的發生,總之算是一個小技巧吧。 我這里用我的打印調試法定位到,代碼會運行到Storage::load這里,我們跟進,在這里我們使用phpstorm直接go to這種方式發現phpstorm定位不到load函數的定義處,那么我們只有先定位Storage類,Storage類如下 發現Storage類里面根本就沒有load方法,而且他也沒有繼承任何父類,那么load方法到底藏在哪里呢?這里就涉及到__callstatic這個模式方法啦,這個方法會在調用該類不存在的靜態方法或變量時觸發,所以,load方式是通過call_user_func_array函數調用的,那到底調用的哪里的load方法呀,這里有兩種方式確定,一是老老實實看代碼,搞清楚self::handler到底值為多少,第二種就是我采用的全局搜索的方法,我不想一行行看代碼,直接全局搜索load( 出來的結果挺多的,但是我們根據之前調用時的參數,可以大體確定是上圖中的其中一個,最后再結合自己的判斷力或者都試一下確定是File.class.php(其實這里我是猜的23333,文件名更貼切嘛)中的load函數,跟進 結果發現,就只是引入了一個文件,我這就急眼了呀,我想這么就引入一個文件就完了呢,那我傳入的content什么時候寫入到這個文件的呀,我覺得我肯定是遺漏了什么東西,于是開始順著這個文件找線索,看看到底哪里把傳入的content寫入了這個文件,還是用我們的打印調試法確定這個文件的路徑在 /data/runtime/Cache/Portal/ 然后文件名的命名規則可以從傳給Storage::load函數的參數里確定 Storage::load(C('CACHE_PATH').$_data['prefix'].md5($_content).C('TMPL_CACHFILE_SUFFIX'),$_data['var']); 我采用了幾種方法來定位到底哪里把content寫入了文件,第一種方式就是全局搜索C(‘CACHE_PATH’).$_data['prefix'].md5($_content).C(‘TMPL_CACHFILE_SUFFIX’) 因為這是文件的命名規則,寫入的時候肯定也是這個規則,但是結果失敗了,只出現一條結果就是load這里,然后我就在想剛剛File.class.php里面有load函數,那么應該也有寫入函數(set,write之類的),結果一看果然有! 那我不得全局搜索一波嘛,在我搜索put的時候有所發現,再根據/data/runtime/Cache/Portal/目錄下生成的cache文件的文件名、文件內容、調用put函數時傳入的實參命名、實參個數以及調用put函數的文件名等多個數據參考,以及失措過后,覺得Template.class.php文件這一處put函數的調用極有可能就是了,這里的loadTemplate函數里有調用put函數的操作,反推,loadTemplate函數又在fetch函數里被調用了,然后我以為我之前跟代碼的時候跟錯了fetch,23333,回到ParseTemplateBehavior.class.php去確認 回到ParseTemplateBehavior.class.php中才發現這個被我忽視的else分支,這里不就調用了template的fetch方法嗎,于是喜上眉梢,那么什么時候會進入else分支呢 這里我做了一個合_Mask理的猜測,就是傳入的參數是之前沒有傳過的,那么就會進入else,否則進入if,然后我在else分支添加了一行echo 444444;然后請求?a=fetch&content=phpinfo(這個請求是之前沒有發送過的) 果然頁面打印處444444,說明進入了else分支,那么content的流向就很清晰了: 先是順著上面的路徑寫入cache文件,最后調用Storage::load加載cache文件,最終導致代碼執行。 啊~這一處的payload就先寫到這吧,好久沒寫文章了,累死了~ *本文原創作者:_Mask,本文屬于FreeBuf原創獎勵計劃,未經許可禁止轉載
站長聲明:以上關於【Thinkcmf任意漏洞包含漏洞分析復現-美滿夫妻生活】的內容是由各互聯網用戶貢獻並自行上傳的,我們新聞網站並不擁有所有權的故也不會承擔相關法律責任。如您發現具有涉嫌版權及其它版權的內容,歡迎發送至:1@qq.com 進行相關的舉報,本站人員會在2~3個工作日內親自聯繫您,一經查實我們將立刻刪除相關的涉嫌侵權內容。