php反序列化学习及简单的反序列化漏洞介绍

php反序列化学习经历

1.为什么会有序列化和反序列化

我们在开发的过程中常常遇到需要把对象或者数组进行序列号存储,反序列化输出的情况。特别是当需要把数组存储到mysql数据库中时,我们时常需要将数组进行序列号操作。

序列化(串行化):是将变量转换为可保存或传输的字符串的过程;

反序列化(反串行化):就是在适当的时候把这个字符串再转化成原来的变量使用。

这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。

2.相关函数

serialize是序列化函数

先看看数组的序列化

<?php

$xx = array('linlin' ,'wwww' ,'aaasss');
 echo serialize($xx)."\n";

输出结果

在这里插入图片描述

反序列化对象

<?php 
    class test
    {
        private $flag = "flag{233}";
        public $a = "aaa";
        static $b = "bbb";
    }

    $test = new test;
    $data = serialize($test);
    echo $data;
 ?>

在这里插入图片描述

O:4:"test":2:{s:10:" test flag";s:9:"flag{233}";s:1:"a";s:3:"aaa";}

这里说明一下序列化字符串的含义:

  1. O:4:"test"指Object(对象) 4个字符:test
  2. :2对象属性个数为2
  3. {}中为属性字符数:属性值

**注意:**可以看到testflag的长度为8,序列化中却显示长度为10。这是因为它是private属性,翻阅文档就可以看到说明,它会在两侧加入空字节

\x00 + 类名 + \x00 + 变量名反序列化出来的是private变量, \x00 + * + \x00 + 变量名反序列化出来的是protected变量 直接变量名反序列化出来的是public变量

unserialize 是反序列化函数

<?php 
    $str = 'O%3A4%3A%22test%22%3A2%3A%7Bs%3A10%3A%22%00test%00flag%22%3Bs%3A9%3A%22flag%7B233%7D%22%3Bs%3A1%3A%22a%22%3Bs%3A3%3A%22aaa%22%3B%7D';
    $data = urldecode($str);
    $obj = unserialize($data);

var_dump($obj);

 ?>	

输出结果如下图
在这里插入图片描述

3.魔术方法

img

比较重要的几个函数:

__sleep()

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。

__wakeup()

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。

__toString()

__toString()方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

__destruct()

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行

__construct()

造方法是对象创建完成后第一个被对象自动调用的方法。在每个类中都有一个构造方法,如果没有显示声明它,那么类中都会默认存在一个没有参数且内容为空的构造方法。通常构造方法被用来执行一些有用的初始化任务,如对成员属性在创建对象时赋予初始值。

其中__toString()触发方式比较多:

  1. echo ( o b j ) / p r i n t ( obj) / print( obj)/print(obj) 打印时会触发
  2. 反序列化对象与字符串连接时
  3. 反序列化对象参与格式化字符串时
  4. 反序列化对象与字符串进行比较时(PHP进行比较的时候会转换参数类型)
  5. 反序列化对象参与格式化SQL语句,绑定参数时
  6. 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
  7. 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
  8. 反序列化的对象作为 class_exists() 的参数的时候

魔术方法的测试

测试代码如下

<?php
class Test{

         public function __construct(){
                  echo 'construct run';
         }
         
         public function __destruct(){
                  echo 'destruct run';
         }

         public function __toString(){
                  echo 'toString run';
         }
         
         public function __sleep(){
                  echo 'sleep run';
         }

         public function __wakeup(){
                  echo 'wakeup run';
         }
}

$test= new Test();
$sTest= serialize($test);
$usTest= unserialize($sTest);
$string= 'hello class ' . $test;
?>

分析一下执行过程:

new了一个对象,对象被创建,执行__construct方法

serialize了一个对象,对象被序列化,先执行__sleep方法,再序列化

unserialize了一个序列化字符串,对象被反序列化,先反序列化,再执行__wakeup方法

把Test这个对象当做字符串使用了,执行__toString方法

程序运行完毕,对象自动销毁,执行__destruct方法

4.根据一题CTF 进一步了解php反序列化漏洞

  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,思路如下:
1、源码最后提示,KEY在flag.php里面;
2、注意到__destruct魔术方法中,有这么一段代码,将file文件内容显示出来
show_source(dirname(FILE).’/‘.$this->file),这个是解题关键;
3、若POST“file”参数为序列化对象,且将file设为flag.php;那么可以通过unserialize反序列化,进而调用__destruct魔术方法来显示flag.php源码(要注意的是file参数内容需要经过base64编码);
4、上面的分析是多么美好,但从代码分析可以知道,还有__wakeup这个拦路虎,通过unserialize反序列化之后,也会调用__wakeup方法,它会把file设为index.php;
5、总结下来就是,想办法把file设为flag.php,调用__destruct方法,且绕过__wakeup。

2、PHP反序列化对象注入漏洞

上网查资料,发现原来这个CTF题目是根据PHP反序列化对象注入漏洞改编的。

简单来说,当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行**。**举个栗子,比如有个Student类,里面有个参数为name。
实际情况:O:7:”Student”:1:{S:4:”name”;s:8:”zhangsan”;}
Payload:O:7:”Student”:2:{S:4:”name”;s:8:”zhangsan”;}
Payload对象属性个数为2,而实际属性个数为1,那么就会掉入漏洞,从而跳过wakeup()方法。

3、CTF Payload

明确了这些之后,就可以构造出Payload了,需反序列化的对象为:

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

O:5:”SoFun” 指的是 类:5个字符:SoFun

:2: 指的是 有两个对象

S:7:”\00*\00file” 指的是有个属性,有7个字符,名为\00*\00file

s:8:”flag.php” 指的是属性值,有8个字符,值为flag.php

值得注意的是,file是protected属性,因此需要用\00*\00来表示,\00代表ascii为0的值。另外,还需要经过Base64编码,结果为:
Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

img

5.参考博客

这个是我的大哥 的 博客:http://www.m00nback.xyz/2019/10/27/php-serialize/

https://blog.csdn.net/Fly_hps/article/details/82736992

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值