一.什么是php序列化
序列化:对象转换成数组或字符串等格式
形式:O:4:"info”:2:{s:4:“name”;i:2:“19”;}
-
O代表object,4为对象长度
-
info为对象名称
-
2为该对象中变量的个数
-
s为string类型
-
4为变量长度
-
name为变量名
-
i代表int,后面同上.
序列化方法:serialize()
<?php
class Animal {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}
$cat = new Animal("mini", 12);
echo serialize($cat);
?>
运行结果:O:6:"Animal":2:{s:4:"name";s:4:"mini";s:3:"age";i:12;}
二.什么是反序列化
反序列化:将数组和字符串转换成对象.
反序列化方法:unserialize()
<?php
class Animal {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}
$a = unserialize('O:6:"Animal":2:{s:4:"name";s:4:"mini";s:3:"age";i:12;}');
var_dump($a);
?>
最后结果就是未编译前的代码
三.魔术方法
什么是魔术方法?
魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。
常见的一些魔术方法及使用:
以下是php常见的魔术方法:
- _construct:构造函数,在创建对象时,会自行调用。
- _destruct:析构函数,在对象的所有引用都被删除时或者对象被销毁时调用。
- _wakeup:进行unserialize时会查看是否有该函数,有的话有限调用,进行初始化对象。
- _toString:当一个类被当成字符串时会被调用。
- _sleep:当一个对象被序列化时调用,可与设定序列化时保存的属性。
- _isset():检测对象的某个属性是否存在是执行此函数.当对不可访问属性调用isset()或empty()时.会触发_isset().
- _unset():当在不可访问的属性上使用unset()时触发 销毁对象的某个属性是执行此函数
- _INVOKE():将对象当作函数来使用时执行此方法,通常不建议这样做.
- _get():用于从不可访问的属性读取数据
- _set():用于将数据写入不可访问的属性
- _callStatci():在静态上下文中调用不可访问的方法时触发
- _call():在对象上下文中调用不可访问的方法时触发
反序列化利用大概分为三类:
- 魔术方法的调用逻辑-如触发条件
- 语言原生类的调用逻辑-如SoapClient
- 语言自身的安全缺陷-如CVE-2016-7124
反序列化例题-POP链构造:
这里以PHPSerialize-labs中class06为例:
在反序列化中我们只能控制成员变量的值,类中的成员方法不可改变,所以在构造payload时成员方法可以去掉,只留下成员变量.
下面是构造代码:
建议新手手搓增加熟练度,这里为了方便使用代码输出.
pop链构造:
下面以PHPSerilize-labs中class13为例进行说明:
<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
- 先看到append方法中有echo $flag,但所有代码中,没有flag这个变量,即flag是在包含文件value中的.又由提示可知,flag在flag.php中,即只要value为flag.php即可,但append方法并非魔术方法,不可以自行调用,所以需要其他魔术方法调用append方法.
- 再次观察代码,发现_invoke魔术方法中调用了append方法,由上面可以知道_invoke的触发条件为将对象当作函数来使用时执行此方法时触发,又见此处append方法中的参数为var,即只要私有属性var的值和value一致为flag.php即可.
- 在Test类中可以看到将function当成函数来使用,即触发_get方法来触发_invoke方法,为变量p创建Modifier对象从而触发_invoke方法.已知_get方法的触发条件为用于从不可访问的属性读取数据,发现可以为Show类中的str变量新建一个实例,达到不可访问source属性从而触发_get方法,因为test类中没有source成员属性.
- _toString的触发条件为当一个类被当成字符串时会被调用。只剩一个_wakeup方法未被调用,且echo $this -> source 当作字符串使用,则为source赋值一个show类实例._wakeup魔术方法的触发条件极其简单进行unserialize时会查看是否有该函数,有的话有限调用,进行初始化对象。
那么POP链就为:
- var=flag.php再创建Modifier对象
- 为Test类创建对象,从而触发_invoke魔术方法
- 触发_get魔术方法,为str赋值test对象
- 将show类对象赋值给source属性,达到触发toString的条件从而完成构造
因为wakeup魔术方法只要出现,反序列化时就会自动触发,所以不用特意触发
因为在序列化过程中我们只能控制成员属性的值,成员方法等不能做出更改,所以构造时可以删除成员方法等.
以下为构造过程:
最后需要注意O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:" Modifier var";s:8:"flag.php";}}}
其中的两个空格需要用%00进行替换,或者直接使用echo urlencode(serialize($c));进行输出