其实我的本意是想学Java反序列化的,因为Java反序列化漏洞很常见,但奈何笔者的Java之旅才刚刚开始,对Java的认识根本还不够,所以就先拿PHP反序列化练练手,至于Java反序列化,就先记着吧!,不过我先上一张图以表我要学Java反序列化的决心!
前言
背景知识
序列化是为了使对象成为字节流来方便存储,以及字节流的方式可以被存储在任何地方,这为我们带来了方便,但同时也带来了安全问题——反序列化漏洞。
正文开始
序列化函数
(PHP4, PHP5, PHP7)
- serialize()
- 将对象转换成字符串
- unserialize()
- 将字符串还原成一个对象
#语法
string serialize (mixed $value)
mixed unserialize (strinf $str)
#参数说明
$value:序列化前的对象或数组
$str:序列化后的字符串
#返回值
serialize(): 字符串
unserialize(): 可解序列化:可为integer,float,string,array或object
不可解序列化:False
先来用两个例子来分别认识PHP的序列化和反序列化.
序列化实例1:
<?php
$CIA = array('Confidentiality','Integrit','Availability');
//CIA:信息安全三要素:机密性,完整性,可用性
$serialized_cia=serialize($CIA);
echo $serialized_cia . PHP_EOL;
?>
执行结果:
我们得到了一个序列化后的字符串:
a:3:{i:0;s:15:"Confidentiality";i:1;s:8:"Integrit";i:2;s:12:"Availability";}
现在再用函数unserialize()去把得到的序列化字符串$serialized_cia给打回原形.
反序列化实例1:
<?php
$str='a:3:{i:0;s:15:"Confidentiality";i:1;s:8:"Integrit";i:2;s:12:"Availability";}';
$unserialized_cia=unserialize($str);
print_r($unserialized_cia);
?>
执行结果:
OK,成功得到序列化之前的数组$CIA:
Array
(
[0] => Confidentiality
[1] => Integrit
[2] => Availability
)
通过上面的两个例子,我们可以对(反)序列化的基本认识,但它的坑,远不止如此。我们再来看个例子.
序列化实例2
<?php
class OWL
{
private $o = "owl{1314}"; //private
public $w = "lll"; //public
static $l = "lll";
}
$owl = new OWL;
$serialized_owl = serialize($owl);
echo $serialized_owl;
?>
执行结果:
O:3:"OWL":2:{s:6:" OWL o";s:9:"owl{1314}";s:1:"w";s:3:"lll";}
这里来了解这个序列化字符串的含义(参考先知)
O:3:"OWL" 是指对象(Object)为3个字符:OWL
:2 是说对象的属性个数为2: private,public
可是" OWL o"的明明为3,而上面却显示为6
这个例子旨在说明:
反序列化可以控制类属性,无论是private还是public
所以上面" OWL o"显示长度为6就是因为它是private属性,因此"OWL"的两侧被加入了空字节,为什么会这样呢?
因为官方文档上写着呢:
文档上还有个值得注意的地方:
Note that static members of an object are not serialized.
这句话的意思是说:对象的静态成员不会序列化
。刚好,这点也在上面的例子中体现了.
或许到了这里,慢慢能对序列化有所悟了,那么我们继续深入,俗话说,知其然,更知其所以然嘛!
魔术方法
在PHP官方文档中,是这样说的:
也就是告诉我们:
在序列化对象时,PHP将在序列化之前尝试调用成员函数 __serialize()或 __sleep()
这是为了允许对象在序列化之前进行last minute clean-up等
同样,当对象是使用还原 反序列化()的__unserialize()或 __wakeup()成员函数被调用
# 上面的last minite clean-up,我没看懂啥意思,如果有大佬知道,还请指点一二,谢谢
划重点:Magic methods
所以下面我们将沿着魔术方法
展开
在PHP中常见的魔术方法有
魔术方法(注意:_ _是两条下划线) | 何时触发/有何作用 |
---|---|
__construct() | 创建对象时触发 |
__destruct() | 对象被销毁时触发 _ |
__call() | 在对象上下文中调用不可访问的方法时触发 _ |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__get() | 用于从不可访问的属性读取数据 |
__set() | 用于将数据写入不可访问的属性 |
__isset() | 在不可访问的属性上调用isset()或empty()触发 _ |
__unset() | 在不可访问的属性上使用unset()时触发 |
__invoke() | 当脚本尝试将对象调用为函数时触发 |
__clone() | 当对象复制完成时调用 |
__toString() | 类被当成字符串时的回应方法 |
__set_state() | 调用var_export()导出类时,此静态方法会被调用 |
__debugInfo() | 打印所需调试信息 |
__sleep() | 执行serialize()时,先会调用这个函数 |
__wakeup | 执行unserialize()时,先会调用这个函数 |
虽然有这么多的函数,但心有余而力不足,这里我只看其中的5个:
- __sleep()
- __wakeup()
- __toString()
- __construct()
- __destruct()
详述如下:
__sleep()
实例:
<?php
class OWL{
private $name;
private $url = 'csdn.com';
public function __construct($name) {
$this->name = $name;
}
public function __sleep() {
return array('url'); //指定要序列化的属性列表
}
}
echo serialize(new OWL('夜猫子'));
?>
执行结果:
序列化时serialize() 函数会检查是否存在一个魔术方法 __sleep() ,如果存在, __sleep() 方法会先被调用, 然后才执行序列化操作。这个功能可以用于清理对象,并返回一个包含对象中所有变量名称的数组。
对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
__wakeup
实例2:
<?php
class OWL{
public function __construct($ID, $sex, $age){
$this->ID = $ID;
$this->sex = $sex;
$this->age = $age;
$this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
}
public function getInfo(){
echo $this->info . '<br>';
}
/**
* serialize前调用 用于删选需要被序列化存储的成员变量
* @return array [description]
*/
public function __sleep(){
echo __METHOD__ . '<br>';
return ['ID', 'sex', 'age'];
}
/**
* unserialize前调用 用于预先准备对象资源
*/
public function __wakeup(){
echo __METHOD__ . '<br>';
$this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
}
}
$owl = new OWL('夜猫子', 21, 'male');
$owl->getInfo();
//存在__sleep(函数,$info属性不会被存储
$temp = serialize($owl);
echo $temp . '<br>';
$owl = unserialize($temp);
//__wakeup()组装的$info
$owl->getInfo();
?>
执行结果:
ID: 夜猫子, age: 21, sex: male<br>OWL::__sleep<br>O:3:"OWL":3:{s:2:"ID";s:9:"夜猫子";s:3:"sex";i:21;s:3:"age";s:4:"male";}<br>OWL::__wakeup<br>ID: 夜猫子, age: 21, sex: male<br>
反序列化时函数unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,然后提前准备好对象所需要的资源。
__toString()
当对象被当作字符串来使用的时候会调用该魔术方法。
实例3:
<?php
class test {
private $flag = '';
# 用于保存重载的数据
private $data = array();
public $filename = '';
public $content = '';
function __construct($filename, $content) {
$this->filename = $filename;
$this->content = $content;
echo 'construct function in test class';
echo "<br>";
}
function __destruct() {
echo 'destruct function in test class';
echo "<br>";
}
# 当需要输出得到对象名称时候会调用
function __toString() {
return $this->content;
}
}
$a = new test('test.txt', 'data');
echo $a."<br>";
?>
执行结果:
打印一个对象的时候被调用,这个方法类似于 java 的 toString 方法,当我们直接打印对象的时候回调这个函数。
__construct()
实例4:
<?php
class test {
private $flag = '';
public $filename = '';
public $data = '';
function __construct($filename, $data) {
$this->filename = $filename;
$this->data = $data;
echo 'construct function in test class';
echo "<br>";
}
}
$a = new test('test.txt', 'data');
?>
执行结果:
当我们对一个对象进行实例化的时候,这个对象的构造方法将会首先被调用.而且,我们知道PHPv5(不只是php) 中的对象模型和类名相同的函数是类的构造函数,那么如果同时定义构造函数和 __construc() 方法的话, php5 会默认调用 __contruct() 而不会调用同类名函数,所以 __contruct() 作为类的默认构造函数
__destruct()
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
实例5:
<?php
class test {
private $flag = '';
public $filename = '';
public $data = '';
function __construct($filename, $data) {
$this->filename = $filename;
$this->data = $data;
echo 'construct function in test class';
echo "<br>";
}
function __destruct() {
echo 'destruct function in test class';
echo "<br>";
}
}
$a = new test('test.txt', 'data');
?>
执行结果:
__clone()
当我们复制一个对象的时候,会用到clone这个关键字,当复制完成后,会自动调用clone魔术方法
实例6:
<?php
class Website{
public function say(){
echo 'Welcome CSDN!<br>';
}
public function __clone(){
echo '克隆成功<br>';
}
}
$obj = new Website();
$obj2 = clone $obj;
echo '<pre>';
var_dump($obj, $obj2);
?>
执行结果:
__invoke()
invoke也是一个比较常用的函数,当对象是用调用函数的方式来使用的时候就会调用invoke魔术方法
.
.
.
(未完,待续)
2020/14/04
先学到这里吧,现在已经凌晨2点了,明天还有网课呢.
附言:
这里简要说明一下,关于序列化学习的博客会有三篇,这是第一篇(待补充);然后明天会有一篇针对于CTF中的各种反序列化题来一次"大屠杀";最后一篇时间未定,得等到我把Java过一遍后,才能来完成对Java反序列化的学习!