PHP反序列化漏洞系列之–PHP序列化和反序列化原理

0.前言

本文爲篤行平常學習記錄,web安全php漏洞系列。php

對象的序列化和反序列化做用就再也不贅述,php中序列化的結果是一個php自定義的字符串格式,有點相似json. html

咱們在任何語言中設計對象的序列化和反序列化都須要解決幾個問題c++

  1. 把某個對象序列化以後,序列化的結果有自描述的功能(從序列化的結果中知道這個對象的具體類型,

    知道類型還不夠,固然還須要知道這個類型所對應具體的值).golang

  2. 序列化時的權限控制,能夠自定義序列化字段等,例如golang中的作的就很是方便.
  3. 時間性能問題:在某些性能敏感的場景下,對象序列化就不能拖後腿,例如:高性能服務(我常常使用protobuf來序列化).
  4. 空間性能問題:序列化以後的結果不能太長,好比內存中一個int對象,序列化以後數據長度變成了10倍int的長度,那這個序列化算法是有問題的.

本文僅僅從php代碼角度來解釋php中序列化和反序列化的過程.,記住一點序列化和反序列化操做的僅僅是對象的數據,這一點有面向對象開發經驗的都應該容易理解.web

1.序列化serialize和反序列化方法unserialize

php原生提供了對象序列化功能,不像c++ ……^_^. 用起來也很是簡單,就兩個接口.算法

class fobnn
{
    public  $hack_id;
    private $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        echo $this->hack_name.PHP_EOL;
    }
}

$obj = new fobnn('fobnn',1);
$obj->print();
$serializedstr = serialize($obj); //經過serialize接口序列化
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);//經過unserialize反序列化
$toobj->print();
fobnn
O:5:"fobnn":2:{s:7:"hack_id";i:1;s:16:"fobnnhack_name";s:5:"fobnn";}
fobnn

看到第二行的輸出,這個字符串就是序列化的結果,這個結構其實很容讀懂,能夠發現是經過對象名稱/成員名稱來映射的,固然不一樣訪問權限的成員序列化以後的標籤名稱略有不一樣.shell

根據我上面講到的3個問題,那麼咱們能夠來看看數據庫

1.自描述功能json

O:5:"fobnn":2 其中o就表示了object類型,且類型名稱爲fobnn, 採用這種格式,後面的2表示了有2個成員對象.數組

關於成員對象,其實也是同一套子描述,這是一個遞歸的定義.

自描述的功能主要是經過字符串記錄對象和成員的名稱來實現.

2.性能問題

php序列化的時間性能本文就不分析了,詳見後面,但序列化結果其實相似json/bson定義的協議,有協議頭,協議頭說明了類型,協議體則說明了類型所對應的值,並不會對序列化結果進行壓縮.

2.反序列化中的魔術方法

對應上述說的第二個問題,其實php中也有解決方法,一種是經過魔術方法,第二種則是自定義序列化函數.先來介紹下魔術方法 __sleep__wakeup

http://php.net/manual/en/lang...

http://php.net/manual/en/lang...

class fobnn
{
    public  $hack_id;
    private $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        echo $this->hack_name.PHP_EOL;
    }

    public  function __sleep()
    {
        return array("hack_name");
    }

    public  function __wakeup()
    {
        $this->hack_name = 'haha';
    }
}

$obj = new fobnn('fobnn',1);
$obj->print();
$serializedstr = serialize($obj);
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);
$toobj->print();
fobnn
O:5:"fobnn":1:{s:16:"fobnnhack_name";s:5:"fobnn";}
haha

在序列化以前會先調用__sleep返回的是一個須要序列化的成員名稱數組,經過這樣咱們就能夠控制須要序列化的數據,案例中我只返回了hack_name,能夠看到結果中只序列化了hack_name成員.

在序列化完成以後,會跳用__wakeup 在這裏咱們能夠作一些後續工做,例如重連數據庫之類的.

3.自定義Serializable接口

自定義序列化接口 http://php.net/manual/en/clas...

interface Serializable {
abstract public string serialize ( void )
abstract public void unserialize ( string $serialized )
}

經過這個接口咱們能夠自定義序列化和反序列化的行爲,這個功能主要能夠用來自定義咱們的序列化格式.

class fobnn implements Serializable
{
    public  $hack_id;
    private $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        echo $this->hack_name.PHP_EOL;
    }

    public  function __sleep()
    {
        return array('hack_name');
    }

    public  function __wakeup()
    {
        $this->hack_name = 'haha';
    }

    public function serialize()
    {
        return json_encode(array('id' => $this->hack_id ,'name'=>$this->hack_name ));
    }

    public function unserialize($var)
    {
        $array = json_decode($var,true);
        $this->hack_name = $array['name'];
        $this->hack_id = $array['id'];
    }
}

$obj = new fobnn('fobnn',1);
$obj->print();
$serializedstr = serialize($obj);
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);
$toobj->print();
fobnn
C:5:"fobnn":23:{{"id":1,"name":"fobnn"}}
fobnn

當使用了自定義序列化接口以後,咱們的魔術方法就沒用了.

4.PHP動態類型和PHP反序列化

既然上文中提到的自描述功能,那麼序列化結果中保存了對象的類型,且php是動態類型語言,那麼咱們就能夠來作個簡單的實驗.

class fobnn
{
    public  $hack_id;
    public $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        var_dump($this->hack_name);
    }
}

$obj = new fobnn('fobnn',1);
$obj->print();
$serializedstr = serialize($obj);
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);
$toobj->print();

$toobj2 = unserialize("O:5:\"fobnn\":2:{s:7:\"hack_id\";i:1;s:9:\"hack_name\";i:12345;}");
$toobj2->print();

咱們修改hack_name反序列化的結果爲int類型, i:12345

string(5) "fobnn"
O:5:"fobnn":2:{s:7:"hack_id";i:1;s:9:"hack_name";s:5:"fobnn";}
string(5) "fobnn"
int(12345)

能夠發現,對象成功序列化回來了!而且能夠正常工做!. 固然php的這種機制提供了靈活多變的語法,但也引入了安全風險. 後續繼續分析php序列化和反序列化特性帶來的安全問題.

最後 ending…若有不足請指點,亦可留言或聯繫 fobcrackgp@163.com.
本文爲篤行原創文章首發於大題小做,永久連接:PHP反序列化漏洞系列之--PHP序列化和反序列化原理

https://www.ifobnn.com/phpserialize.html
相關文章
相關標籤/搜索