目录
php序列化和反序列化简介
序列化
将对象状态转换为可保持或可传输的格式的过程。
简单的理解:将 PHP 中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中。
反序列化
反序列化就是再将这个状态信息拿出来使用。(将字符串重新再转化为对象或者其他的状态信息)
简单来说:将这个状态信息转换成原来的对象或者其他原来的格式。
PHP序列化:把对象转化为二进制的字符串 ,使用serialize()
函数 PHP反序列化:把对象转化的二进制字符串再转化为对象 ,使用unserialize()
函数
类中定义的属性
public:属性被序列化的时候属性值会变成 属性名
protected:属性被序列化的时候属性值会变成 \x00*\x00属性名
private:属性被序列化的时候属性值会变成 \x00类名\x00属性名
其中:\x00
表示空字符,但是还是占用一个字符位置(空格)
序列化实例
序列化只序列属性,不序列方法
当在 php 中创建了一个对象后,可以通过 serialize() 把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用。
<?php
// 定义一个简单的类
class Person {
public $name='Alice';
public $age='25';
}
// 创建一个Person对象
$person = new Person();
// 序列化对象
$serialized_object = serialize($person);
// 输出序列化后的字符串
echo $serialized_object
?>
输出结果
O:6:"Person":2:{s:4:"name";s:5:"Alice";s:3:"age";s:2:"25";}
反序列化实例
与 serialize() 对应的, unserialize() 可以从已存储的表示中创建 PHP 的值,可以从序列化后的结果中恢复对象( object )
<?php
// 定义一个简单的类
class Person {
public $name;
public $age;
}
// 定义要反序列化字符串
$object = 'O:6:"Person":2:{s:4:"name";s:5:"Alice";s:3:"age";s:2:"25";}';
// 反序列化字符串为对象
$unserialized_object = unserialize($object);
// 输出反序列化后的对象信息
echo "Name: " . $unserialized_object->name . "\n";
echo "Age: " . $unserialized_object->age . "\n";
?>
输出结果
Name: Alice Age: 25
反序列化漏洞
本质上 serialize() 和 unserialize() 在 PHP 内部实现上是没有漏洞的,漏洞的主要产生是由于应用程序在处理对象、魔术函数以及序列化相关问题的时候导致的。
当传给 unserialize() 的参数可控时,那么用户就可以注入精心构造的payload 。当进行反序列化的时候就有可能会触发对象中的一些魔术方法,造成意想不到的危害。
序列化返回的字符串格式
O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>}
O:表示序列化的是对象
<length>:表示序列化的类名称长度
<class name>:表示序列化的类的名称
<n>:表示被序列化的对象的属性个数
<field name 1>:属性名
<field value 1>:属性值
$number = 34;
$str = 'uusama';
$bool = true;
$null = NULL;
$arr = array('a' => 1, 'b' => 2);
$cc = new CC('uu', true);
var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
var_dump(serialize($cc));
输出结果
string(5) "i:34;"
string(13) "s:6:"uusama";"
string(4) "b:1;"
string(2) "N;"
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
string(52) "O:2:"CC":2:{s:4:"data";s:2:"uu";s:8:" CC pass";b:1;}"
序列化对于不同类型得到的字符串格式为:
-
String
: s:size:value; -
Integer
: i:value; -
Boolean
: b:value;(保存1或0) -
Null
: N; -
Array
: a:size: -
Object
: O:strlen(object name):object name:object size:
魔术方法和反序列化利用
php 中有一类特殊的方法叫" Magic function" (魔术方法), 这里我们着重关注一下几个:
__construct()当一个对象创建时被调用,但在 unserialize()时是不会自动调用的。(构造函数)
__destruct()当一个对象销毁时被调用
__toString()当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup将在序列化之后立即被调用
从序列化到反序列化这几个函数的执行过程是:
__construct()` ->`__sleep()` -> `__wakeup()` -> `__toString()` -> `__destruct()
绕过wakeup
CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行(让n大于原有的对象属性个数)
官方给出的影响版本: PHP5 < 5.6.25
PHP7 < 7.0.10
靶场实战
payload:
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
分析代码:
__construct()
在序列化的时候会自动调用,反序列化时不会自动调用。
这里着重分析一下if语句
if(!@$unser = unserialize($s)){
$html.="<p>大兄弟,来点劲爆点儿的!</p>";
}else{
$html.="<p>{$unser->test}</p>";
}
它的作用如下:
-
unserialize($s)
: 这个函数尝试将一个序列化的字符串 ($s
) 转换回PHP的值(比如数组、对象等)。如果$s
不是一个序列化的字符串或在反序列化过程中出现错误,unserialize()
函数会返回false
。 -
@$unser
: 这部分使用了错误控制运算符 (@
)。它会抑制unserialize()
可能抛出的任何错误。如果unserialize()
失败(返回false
),那么$unser
也会是false
。 -
若反序列化失败,或者说s不是一个序列化后的值,`unserialize(s)
";`返回
false给
unser,@用来一直错误输出,
!false为真,执行语句
html.="大兄弟,来点劲爆点儿的!
";`若反序列化成功,`unserialize(s)返回
true给
unser,
!true为假,执行else后面的语句
html.="{
unser->test}
因为是直接嵌入html页面中,没有经过过滤,所以可以输入前端代码造成xss攻击。
修复方法
-
验证输入: 在接收用户输入并执行反序列化之前,验证输入的合法性和预期格式。可以使用正则表达式或其他方法检查输入是否符合预期的序列化字符串格式。
-
过滤输出 : 在输出反序列化后的对象属性时,确保适当地转义和过滤用户提供的内容,以防止恶意代码执行。可以使用 PHP 的
htmlspecialchars()
函数来转义输出,确保任何 HTML 标签都被安全地显示。 -
限制反序列化的对象类型: 如果可能的话,尽量避免反序列化不受信任的数据,尤其是复杂的对象结构。可以使用简单的数据结构或者明确指定允许的类名。