文章目录
- 前言
- [1. php序列化与反序列化](#1. php序列化与反序列化)
-
- [1.1 序列化与反序列化原理](#1.1 序列化与反序列化原理)
- [1.2 php中的序列化与反序列化函数](#1.2 php中的序列化与反序列化函数)
- [2. 魔术方法/函数](#2. 魔术方法/函数)
- [3. PHP 对象注入/反序列化漏洞](#3. PHP 对象注入/反序列化漏洞)
-
- [3.1 foxcms反序列化漏洞(CVE-2025-29036)](#3.1 foxcms反序列化漏洞(CVE-2025-29036))
- [3.2 POP链](#3.2 POP链)
- [3.2 phar](#3.2 phar)
- [4. ThinkPHP反序列化漏洞](#4. ThinkPHP反序列化漏洞)
- 参考
前言
PHP反序列化漏洞,又叫PHP对象注入漏洞,直接叫对象注入漏洞(Object Injection)更贴切,这对于我们理解这个漏洞更有帮助。本文需要你有一定的编程基础,起码知道并理解OOP(面向对象)、类、对象这3个概念。
1. php序列化与反序列化
1.1 序列化与反序列化原理
关于序列化与反序列化,彻底理解序列化与反序列化(基于C/Java)这篇文章介绍的蛮清楚了。总结起来就是:
内存中的"数据"不是裸字节,而是带有类型、指针、内存布局等语义的结构。这些语义只有当前运行环境能理解。序列化就是把这些环境相关的结构翻译成通用的、可传输的格式。
比如你new一个Person对象
php
class Person{
public $age;
public $name;
public $weight;
}
$person = new Person(18, "小明", 100);
实际在内存中的布局是一系列的地址引用,如果直接把对象所指的该段内存直接复制给另一个进程的话,是一段无效的内容。就像下图中的$person对象,0x43004300这个地址指向"小明"这个字符串,但是复制到另一台机器或进程时,目标进程里的0x43004300不知道是啥,还可能引发内存访问异常。

所以,我们要把引用类型的数据补全------引用修复(Reference Fixup) ,即要发送的数据里要包括数据值。即便我们把内存还原到上图"我们认为的内存数据状态",还是不行。我们怎么知道$name这个变量的长度是多少呢?去读一串数据的时候,$name字段要读多少字节?这个对象是"小明",下一个对象是"王小美"怎么办。
所以,双方要约定一个规范的格式,规定每个字节代表什么,把内存中的数据按规定组织起来,就是序列化。你可能会想,我们要的对象不就是new Person(18, "小明", 100)吗,这是你写源代码时的语境,我们要把这些环境/语境/上下文信息一起发给对方,对方才能理解并解析我们发送的这一段连续的字节。
下图右侧我们平常写的代码,看到这个类,任何人都可以还原基于这个类的对象。代码经过编译或者解释翻译,在内存中并不是像我们能看到变量=值这种形式,只是一些引用/指针/地址,就像下图。将内存中这些地址/数据直接传递,没有任何作用,因为没人能够理解这一串数据是什么。

要想将内存中的这个对象(一些数据)发送给另一方,对方可以将其还原为一个可解析处理的对象,我们大概需要哪些信息?
- 类名
- 对象名
- 对象的属性数量
- 属性名、属性的类型、属性的值...
也就是说,我们需要一套规范化的格式,描述这个对象。实现了将内存中的对象转化为一个可传递的字符串的过程就叫做序列化,反之,就称为反序列化。PHP中对象序列化后的格式:
O:类名长度:"类名":变量数:{变量类型:变量名长度:"变量名字";变量的值;}
- 序列化:将数据结构或对象转换为一串字节流/字符流的过程。使其可以在存储、传输或缓存时进行持久化。
- 反序列化:将序列化后的数据进行解码和还原,恢复为原始的数据结构或对象的过程
1.2 php中的序列化与反序列化函数
明白了序列化,反序列化的原理就不在话下了。接下来,我们来实践一下php中序列化/反序列化的功能实现。
在 PHP 中,使用serialize() 函数可以将数据(php中所有的数据类型)和对象进行序列化,得到一个表示序列化后数据的字符串。通过unserialize() 函数可以将这个字符串进行反序列化,将其还原为原始的数据对象。

我们用php在线运行平台运行下图代码,可以看到反序列化后的字符串格式如下,关于格式的更详细解释,请百度一下或看一下参考3。

2. 魔术方法/函数
学习面向对象编程,应该都学过构造函数/方法。因为初始化很重要,所以对象一创建,高级语言的编译器会自动调用构造函数来初始化变量,也有一种说法是通过构造方法来创建对象的,我们不深究这个。构造方法是PHP中魔术方法的一种,PHP的魔术方法(Magic Methods)是一组特殊的方法,以双下划线(__)开头和结束命名的。它们在对象的生命周期中被自动调用,用于执行特定的操作。这些魔术方法可以让开发者更好地控制和定制对象的行为。
__construct(), 类的构造函数,创建对象时进行初始化操作
__destruct(), 类的析构函数,在对象被销毁(即失去对对象的所有引用)之前执行一些清理操作
__call(), 在对象中调用一个不可访问或不存在方法时调用
__callStatic(), 调用一个不可访问或不存在的静态方法时调用
__get(), 访问一个对象的不可访问或不存在属性时调用
__set(), 对不可访问属性进行赋值时调用
__isset(), 当对不可访问或不存在属性调用isset()或empty()时调用
__unset(), 当对不可访问或不存在属性调用unset()时被调用。
__sleep(), 执行serialize()之前,先会调用这个函数
__wakeup(), 执行unserialize()之后调用这个函数
__toString(), 类被当成字符串时的回应方法
__invoke(), 用于将一个对象作为函数直接调用时的行为定义。
__set_state(), 设置对var_export() 函数所产生的字符串的进行反序列化操作时行为。
__clone(), 当clone 关键字复制一个对象时调用
__autoload(), 尝试加载未定义的类
__debugInfo(), 打印所需调试信息
我们再复活一次王小美,注意观察,我们没有显式调用__wakeup(),是unserialize()内部自动调用
php
<?php
class Person {
public $age;
public $name;
public $weight;
public function speak(){
echo "<br>><我是王小美<br>";
}
public function __wakeup(){
echo "__wakeup() called!复活吧,我的对象------王小美<br>";
}
}
echo "<h1>php中的序列化与反序列化函数</h1>";
echo "new 了个对象:王小美<br>";
$wxm = new Person();
$wxm->age = 18;
$wxm->name = "王小美";
$wxm->weight = 150.0;
$wxm_serialized = serialize($wxm);
echo "<h2>将对象 王小美 序列化</h2>";
echo "对象的数据被保全在一个字符串里!<br>";
echo $wxm_serialized;
echo "<h2>将对象 王小美 反序列化</h2>";
echo "以往,创建对象需要 new 一个,然后让编译器帮我生成<br>";
echo "现在,我可以让\"沉睡\"的对象复活!<br>";
$wxm_unserialized = unserialize($wxm_serialized);
$wxm_unserialized->speak();
?>
运行结果如下:

3. PHP 对象注入/反序列化漏洞
有了什么是反序列化和魔术方法的概念,那就要开始我们的PHP对象注入/反序列化漏洞之旅了。
假如让我们写一个靶场,最简单的就是:有一个类,它的方法/函数会将接收输入并解析执行
php
<?php
highlight_file(__FILE__);
class Target {
public $input;
function __wakeup(){
echo system($this->input);
}
}
$a = new Target();
$a->input = "whoami";
$a_serialized = serialize($a);
$a_unserialized = unserialize($a_serialized);
?>
运行结果如下:

我们把它改造为一个web应用

这看起来像一个直接的RCE,不妨改成这样子:该WebApp有一个发序列化的接口,且我们知道该PHP环境中存在一个Target类。

所以,直接反序列化前端提交的输入,是一件及其危险的事情。当然,实战中,极少情况目标服务器会有一个类,类里面的函数直接执行前端提交的输入,下文会介绍我们仍可以构造一个 POP链来完成攻击。
3.1 foxcms反序列化漏洞(CVE-2025-29036)
环境搭建就是将源码下载下来,扔进phpstudy,然后按要求下一步下一步就好。网上关于这个漏洞的入口点都是/images/index.html?id=,笔者在自己本地搭建的apache +php环境中一直有奇怪的问题复现不了,后来无缘无故又行了。我们不深究这些问题,来看看漏洞触发的过程:

可以看到,这里接收一个参数id,用于请求的页面。我们将id的值替换为payload:${@print_r(@system("whoami"))},执行并回显了

3.2 POP链
3.2 phar
4. ThinkPHP反序列化漏洞
参考
1\] [PHP对象的内存模型](https://cloud.tencent.com/developer/article/1679545) \[2\] [PHP对象类型在内存中的分配](https://www.cnblogs.com/North-South/p/8877042.html) \[3\] [php反序列化漏洞教程-零基础教程](https://www.bilibili.com/video/BV1TH4y1U7tJ/?share_source=copy_web&vd_source=1ad2de4ab6813fea893ee5a020dd8303) \[4\] [PHP魔术方法-官方文档](https://www.php.net/manual/zh/language.oop5.magic.php) \[5\] [serialize-官方文档](https://www.php.net/manual/zh/function.serialize.php) \[5\] [deepseek](https://www.deepseek.com) \[6\] [mimo](https://aistudio.xiaomimimo.com/)