序列化:对象转换为数组或字符串等格式
反序列化:将数组或字符串等格式转换成对象
serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象
PHP 反序列化漏洞
原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而 导致代码执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些 魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法
{PHP的反序列化漏洞又可以叫做 PHP对象注入漏洞,这种思想类似SQL注入,在unserialize接受的参数可控的情况下,通过注入我们可控的属性值,来控制类中的方法(危险函数)的执行,从而造成安全隐患。}
php魔术方法利用点分析:
先了解一下什么是魔术方法: PHP: 魔术方法 - Manual
PHP 将所有以 (两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以__为前缀。
触发unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法:
__construct(): //构造函数,当对象 new 的时候会自动调用
__destruct()://析构函数当对象被销毁时会被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__call(): //在对象上下文中调用不可访问的方法时触发
__callStatic(): //在静态上下文中调用不可访问的方法时触发
__get(): //用于从不可访问的属性读取数据
__set(): //用于将数据写入不可访问的属性
__isset(): //在不可访问的属性上调用 isset()或 empty()触发
__unset(): //在不可访问的属性上使用 unset()时触发 __
__toString(): //把类当作字符串使用时触发 __
__sleep(): //serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
示例:
<?php
// 定义一个名为 magic 的类
class magic {
// 类的公共属性 $name,初始化为 "Annevi"
public $name = "Annevi";
// 构造函数,在创建对象时调用
function __construct() {
// 输出构造函数被调用的信息
echo "__construct";
echo "<br>";
}
// 析构函数,在对象被销毁时调用
function __destruct() {
// 输出析构函数被调用的信息
echo "__destruct";
echo "<br>";
}
// 当对象被反序列化时调用的魔术方法
function __wakeup() {
// 输出 __wakeup 方法被调用的信息
echo "__wakeup";
echo "<br>";
}
// 当对象被当作字符串使用时调用的魔术方法
function __toString() {
// 返回一个字符串,表示对象被转换为字符串时的值
return "__toString" . "<br>";
}
}
// 创建一个 magic 类的实例
$obj = new magic(); // 调用 __construct,输出 "__construct<br>"
// 输出 "1 <br>"
echo "1 <br>";
// 序列化 $obj 对象,将其转换为字符串
$data = serialize($obj); // 此时不调用任何魔术方法
// 输出 "2<br>"
echo "2<br>";
// 反序列化字符串 $data,将其转换回对象
$un_obj = unserialize($data); // 调用 __wakeup,输出 "__wakeup<br>"
// 输出 "3 <br>"
echo "3 <br>";
// 输出对象的字符串表示,调用 __toString 方法,输出 "__toString<br>"
print($un_obj); // 注意这里使用了 print,它和 echo 功能相似,但通常用于单个值
// 输出 "4 <br>"
echo "4 <br>";
// 脚本结束时,$un_obj 对象不再被引用,调用 __destruct,输出 "__destruct<br>"
// 注意:析构函数的调用时机可能因 PHP 的垃圾回收机制而异,但通常会在脚本结束时调用
输出结果:
__construct // 构造函数在创建对象时被调用
1 // 第一个 echo 语句
2 // 第二个 echo 语句
__wakeup // 反序列化时调用 __wakeup 方法
3 // 第三个 echo 语句
__toString // 对象被当作字符串输出时调用 __toString 方法
4 // 第四个 echo 语句
__destruct // 脚本结束时调用析构函数(可能因垃圾回收机制而有所延迟)
从这里我们可以看到,在创建一个对象的时候,首先调用了construct方法,紧接着执行序列化操作,并没有触发调用任何的魔术方法,之后执行反序列化操作,自动调用了wakeup方法,之后我们使用print输出对象,调用了toString,最后销毁实例化创建的对象和我们反序列化生成的对象,调用destruct两次。
<?php
//定义类
class Test
{
//定义两个变量
public $variable = 'BUZZ';
public $variable2 = 'OTHER';
//定义方法
public function PrintVariable()
{
echo $this->variable . '<br />';
}
public function __construct()
{
echo '__construct<br />';
}
public function __destruct()
{
echo '__destruct<br />';
}
public function __wakeup()
{
echo '__wakeup<br />';
}
public function __sleep()
{
echo '__sleep<br />';
return array('variable', 'variable2');
}
public function __toString() {
return "Test object";
}
}
// 创建对象调用__construct
$obj = new Test();
// 序列化对象调用__sleep
$serialized = serialize($obj);
// 输出序列化后的字符串
print 'Serialized: ' . $serialized . '<br />';
// 重建对象调用__wakeup
$obj2 = unserialize($serialized);
echo $obj2.'<br>';
// 调用PrintVariable输出数据
$obj2->PrintVariable();
// 脚本结束调用__destruct
?>