反序列化逃逸
摘录https://www.cnblogs.com/wjrblogs/p/12800358.html
序列化和反序列化的概念#
序列化就是将 对象、string、数组array、变量
转换成具有一定格式的字符串。
具体可以看 CTF PHP反序列化,下图摘自此篇文章
其实每个字符对应的含义都很好理解:
s ---- string 字符串
i ---- integer 整数
d --- 双精度型
b ---- boolean 布尔数
N ---- NULL 空值
O ---- Object 对象
a ---- array 数组
·············
其中较为重要的是 对象 的序列化与反序列化:
对象序列化后的字符串包括 属性名、属性值、属性类型、该对象对应的类名,注意并不包括类中的方法
序列化:
将对象转化成字符串存储
其中:
序列化一个实例对象后:
O:4:"Test":3:{s:4:"name";s:6:"R0oKi3";s:6:"*age";s:16:"18岁余24个月";s:10:"Testmoto";s:6:"hehehe";}
O:4:"Test":3: ---O 代表 对象,Test为其类名,占 4 个字符,并且有 3 个属性
{} ---大括号里面包含具体的属性
s:4:"name";s:6:"R0oKi3"; ---以分号分隔属性名和属性值,s 表示字符串,4、6 表示字符长度,name表示属性名,R0oKi3 表示属性值,后续一样,都是成对的
注意点:当访问控制修饰符(public、protected、private)不同时,序列化后的结果也不同
public 被序列化的时候属性名 不会更改
protected 被序列化的时候属性名 会变成 %00*%00属性名
private 被序列化的时候属性名 会变成 %00类名%00属性名
由于 %00 就是一个空字符,所以不会显示出来,不过为了显示效果,在菜鸟工具上可以明显看到不同
当提交 payload 的时候就需要将 %00 给加上后再提交
反序列化:
反序列化则相反,其将字符串转化成对象
至于为什么要序列化:
对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。
serialize() 函数
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,__sleep() 方法会先被调用,然后才执行序列化操作。
unserialize() 函数
unserialize() 会检查是否存在一个 __wakeup() 魔术方法,成功地重新构造对象后,如果存在__wakeup() 成员函数则会调用 __wakeup()
但是当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过 __wakeup() 的执行,也就是 CVE-2016-7124,php 版本限制(PHP5 < 5.6.25、PHP7 < 7.0.10)。
由于会从字符串构造出对象,那么会不会调用 __construct() 构造函数?
例如如下测试代码:
<?php
class Test{
public $test;
function __construct(){
$this->test = '0默认内容<br>';
echo $this->test;
}
}
$a = new Test();
echo '1'.$a->test;
$a->test = '自定义内容<br>';
echo '2'.$a->test;
$x = serialize($a);
$y = unserialize($x);
echo '3'.$y->test;
?>
执行结果为:
0默认内容 // $a = new Test(); 时执行 __construct() 构造函数
1默认内容 // echo '1'.$a->test;
2自定义内容 // echo '2'.$a->test;
3自定义内容 // echo '3'.$y->test;
可以看到反序列化时,并没有调用 __construct() 构造函数。
- 其他可能会用到的 魔术方法
__destruct() 析构函数,当对象被销毁或者程序退出时会自动调用
__toString() 用于一个类被当成字符串时触发
__invoke() 当尝试以调用函数的方式调用一个对象时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用 isset() 或 empty() 触发
__unset() 在不可访问的属性上使用 unset() 时触发