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

PHP反序列化漏洞实战:从CVE-2016-7124绕过__wakeup到CTF解题

PHP反序列化漏洞实战:从CVE-2016-7124绕过__wakeup到CTF解题
📅 发布时间:2026/6/22 7:45:19

1. 项目概述:从一道CTF题看PHP反序列化的攻防博弈

最近在带新人入门Web安全,发现很多朋友对PHP反序列化漏洞的理解还停留在“知道有这么回事”的层面,一到实战就无从下手。正好,攻防世界(CTFHub)里那道经典的unserialize3题目,完美地浓缩了这类漏洞的一个关键考点——如何绕过__wakeup魔术方法。这不仅仅是解一道题拿个flag那么简单,它背后折射的是对PHP对象生命周期和序列化机制的深度理解。今天,我就以这道题为蓝本,手把手带你拆解整个漏洞利用过程,从原理分析、代码审计到最终Payload构造,让你不仅知其然,更知其所以然。无论你是刚接触安全的新手,还是想巩固反序列化知识的老兵,这篇实战笔记都能给你带来直接的收获。我们最终的目标很明确:绕过__wakeup的“防御”,拿到藏在服务器里的flag。

2. 漏洞原理深度解析:序列化、反序列化与魔术方法

要绕过__wakeup,首先得彻底明白它在整个流程中扮演什么角色。这得从PHP序列化(serialize)和反序列化(unserialize)这两个基础操作说起。

2.1 序列化与反序列化:数据的“打包”与“解包”

你可以把PHP的序列化想象成给一个复杂的、活生生的对象“拍一张快照”。这个对象可能有各种属性(数据),还定义了一系列方法(行为)。serialize()函数的作用,就是把这个对象当前的状态(主要是属性值)转换成一个字符串格式的“字节流”。这个字符串包含了重建该对象所需的最少信息,比如对象的类名、属性名和属性值。这样做的好处是方便存储(比如存到数据库或文件里)或传输(比如通过网络发送)。

而unserialize()函数则相反,它是个“复活”过程。它读取这个序列化字符串,并根据其中的信息,在内存中重新创建出一个和原来状态几乎一模一样的对象实例。注意,这里说的是“几乎”,因为序列化主要保存的是对象的数据(属性),而不是其逻辑(方法的代码)。方法的逻辑是由类的定义决定的。

2.2 魔术方法:对象生命周期的“事件监听器”

PHP提供了一系列以双下划线__开头的魔术方法(Magic Methods),它们会在对象的特定生命周期节点被自动调用。在反序列化漏洞的利用中,以下几个尤为关键:

  • __construct(): 构造函数,在对象被创建(new)时调用。
  • __destruct(): 析构函数,在对象被销毁(如脚本执行结束、被unset)时调用。这是反序列化漏洞中最常见的“攻击入口”之一,因为反序列化出来的对象在脚本结束时总会触发析构。
  • __wakeup(): 在对象被unserialize()反序列化之后、自动调用之前执行。它的设计初衷是用于反序列化后,重新初始化一些可能丢失的资源(比如数据库连接、文件句柄)。

这里就是unserialize3题目的核心考点:__wakeup方法的存在,常常被开发者用作一种“安全措施”。他们可能会在__wakeup里重置对象状态、进行一些安全检查,甚至直接销毁对象或退出流程,从而阻断我们利用__destruct或__toString等后续魔术方法执行恶意代码的企图。

2.3__wakeup绕过原理(CVE-2016-7124)

那么,__wakeup就真的无法逾越吗?并非如此。PHP历史上存在一个著名的特性,在特定版本下可以被利用来绕过__wakeup的调用。这个特性与序列化字符串中表示对象属性数量的值有关。

一个标准的序列化字符串格式如下:O:<类名长度>:"<类名>":<属性数量>:{<属性序列化>...}

例如,一个TestClass类,有一个属性$a,值为1,序列化后可能是:O:10:"TestClass":1:{s:1:"a";i:1;}

这里的1就表示这个对象有1个属性。

绕过关键点:在PHP 5.6.25之前和PHP 7.0.10之前的版本中,如果我们在序列化字符串中,将对象属性数量的值,修改为比实际属性数量更大的数字,那么在反序列化时,__wakeup方法将不会被调用,但对象依然会被成功反序列化,并且后续的__destruct方法会正常执行。

这就是我们绕过__wakeup的武器。对于上面的例子,我们将字符串改为:O:10:"TestClass":2:{s:1:"a";i:1;}(注意,属性数量从1改成了2,但后面属性的定义并没有增加)

当这个字符串被unserialize()处理时,PHP会尝试读取2个属性,但实际数据只定义了1个,这会导致解析异常。然而,在受影响版本的PHP中,这种异常恰好阻止了__wakeup的执行,却不妨碍对象被创建以及最终__destruct的触发。

注意:这个绕过方法有严格的版本限制。在解题或实战中,首要步骤就是判断目标PHP版本是否在受影响范围内。攻防世界的unserialize3题目环境通常就是搭建在存在此漏洞的PHP版本上,为我们创造了条件。

3. 靶场环境分析与代码审计思路

明确了原理,我们就要开始实战了。面对unserialize3这样的题目,标准的解题流程是:获取源码 -> 代码审计 -> 寻找漏洞点 -> 构造Payload。

3.1 获取题目源码

CTF题目,尤其是Web题,经常通过留备份文件、.git泄露、注释提示等方式提供源码。对于unserialize3,常见的方法是访问index.php的备份文件,如index.php.bak、index.php~,或者尝试www.zip、source.zip等压缩包。有时候直接查看网页源代码也能找到线索。这里我们假设通过常规扫描,获取到了核心的PHP源码文件。

3.2 核心漏洞代码审计

假设我们拿到了如下简化后的源码(class.php):

<?php class xctf{ public $flag = '111'; public function __wakeup(){ exit('bad requests'); } public function __destruct(){ // 我们假设,在理想情况下,这里会输出或操作$flag // 例如:echo $this->flag; // 但题目可能把真正的flag放在服务器文件里,这里只是示意 // 真正的目标可能是触发这里,从而读取flag文件 if (isset($this->flag)) { // 一些关键操作... } } } ?>

以及一个入口文件(index.php):

<?php require_once('class.php'); $str = $_GET['code']; if (isset($str)) { $data = unserialize($str); echo "Welcome!"; } else { highlight_file(__FILE__); } ?>

审计过程:

  1. 定位反序列化入口:index.php中,通过$_GET['code']获取参数,并直接传递给unserialize()函数。这是一个明显的、用户输入可控的反序列化点。
  2. 分析可利用的类:代码中只定义了一个类xctf。
  3. 寻找魔术方法:
    • __wakeup(): 该方法直接执行exit('bad requests')。这意味着,只要__wakeup被调用,程序会立即终止,打印“bad requests”,我们后续的任何企图都会落空。这是我们必须绕过的障碍。
    • __destruct(): 析构函数。这里虽然看起来只是判断$flag是否存在,但在真实的题目场景中,__destruct内部可能包含文件读取、命令执行等关键代码,或者是触发其他链式调用的起点。我们的目标就是让程序执行到这里。
  4. 分析属性:类有一个公共属性$flag。在反序列化时,我们可以通过序列化字符串控制这个属性的值。

解题思路链:我们需要向code参数传递一个精心构造的序列化字符串。这个字符串要能:

  • 成功反序列化出一个xctf对象。
  • 绕过__wakeup方法,防止程序退出。
  • 让对象正常走到生命周期结束,从而自动调用__destruct方法,执行其中的关键代码(在真实题目中,这可能是获取flag的关键)。

3.3 确定利用链与攻击面

对于这道题,利用链非常直接:可控输入($_GET[‘code’])->unserialize()->绕过__wakeup->对象销毁->触发__destruct。

攻击面就在于我们能否控制序列化字符串,使其在反序列化时触发漏洞。结合第2.3节的知识,我们确定使用修改属性数量的方法来尝试绕过__wakeup。

4. 手把手构造绕过Payload

理论结合实践,现在我们一步步构造出能绕过__wakeup的Payload。

4.1 步骤一:创建正常对象并序列化

我们先写一个本地脚本,模拟创建对象并生成标准的序列化字符串。

<?php class xctf{ public $flag = '111'; // 初始值不重要,我们可以覆盖它 } $obj = new xctf(); // 我们可以修改$flag的值,比如指向一个假想的flag文件 // $obj->flag = '/path/to/real/flag'; echo serialize($obj); ?>

运行这段代码,会得到类似以下的输出:O:4:"xctf":1:{s:4:"flag";s:3:"111";}

字符串解析:

  • O: 表示对象(Object)。
  • 4: 类名xctf的长度。
  • "xctf": 类名。
  • 1: 对象属性的数量(本例中只有$flag一个属性)。
  • {s:4:"flag";s:3:"111";}: 这是属性的序列化。s:4:"flag"表示一个长度为4的字符串属性名flag;s:3:"111"表示一个长度为3的字符串属性值111。

4.2 步骤二:应用__wakeup绕过技巧

根据CVE-2016-7124,我们需要将属性数量1修改为一个大于实际属性数量的数字,比如2或100。同时,为了增加利用成功率,我们可能还需要修改$flag属性的值。在真实题目中,__destruct方法里可能会用$this->flag去做文件读取(例如file_get_contents($this->flag)),那么我们就需要将$flag的值设置为服务器上存储flag的真实路径(这通常需要结合其他信息泄露或路径遍历漏洞来获取,有时题目会直接给提示)。

假设我们通过信息收集,知道flag文件在/flag。那么,我们先构造一个属性值被修改的序列化字符串:O:4:"xctf":1:{s:4:"flag";s:5:"/flag";}

然后,应用绕过技巧,将属性数量1改为2:O:4:"xctf":2:{s:4:"flag";s:5:"/flag";}

这就是我们的核心Payload。

4.3 步骤三:进行URL编码与传输

由于Payload需要通过GET请求的code参数传递,而序列化字符串中包含花括号{}、引号"等特殊字符,在URL中可能会被错误解析或截断。因此,我们需要对其进行URL编码。

可以使用在线工具或编程语言函数(如PHP的urlencode)进行编码。上述Payload编码后大致如下:O%3A4%3A%22xctf%22%3A2%3A%7Bs%3A4%3A%22flag%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D

4.4 步骤四:发起攻击并获取结果

在浏览器中访问靶场地址,并附上我们的Payload:http://靶场地址/?code=O%3A4%3A%22xctf%22%3A2%3A%7Bs%3A4%3A%22flag%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D

如果一切顺利:

  1. 服务器接收到code参数。
  2. unserialize()函数开始解析我们提供的字符串。
  3. 由于属性数量(2)大于实际定义的数量(1),在存在漏洞的PHP版本中,__wakeup方法被跳过。
  4. 一个xctf对象被成功创建,其$flag属性值为/flag。
  5. 脚本执行到末尾,该对象被销毁,触发__destruct()方法。
  6. 在__destruct()方法中(根据题目实际代码),可能会读取/flag文件的内容并将其输出到页面,或者作为响应的一部分返回。这样,flag就出现在我们眼前了。

5. 实战中的疑难排查与技巧进阶

在实际操作中,事情往往不会一帆风顺。下面分享几个我踩过的坑和对应的排查思路。

5.1 常见问题排查表

问题现象可能原因排查思路与解决方案
页面返回“bad requests”__wakeup方法未被成功绕过1.确认PHP版本:这是最常见的原因。靶场环境可能使用了已修复该漏洞的PHP版本(>=5.6.25或>=7.0.10)。尝试寻找其他入口或利用链。
2.检查Payload格式:仔细核对序列化字符串的语法,确保花括号、分号、引号配对正确,属性名长度与实际字符串长度严格一致。一个字符的错误都会导致解析失败,从而可能走正常流程触发__wakeup。
页面空白或报错(非“bad requests”)反序列化过程出错1.URL编码问题:确保Payload已正确进行URL编码。可以先用urldecode函数验证一下编码后的字符串是否与原始Payload一致。
2.属性名修饰符:如果类属性是private或protected,其序列化后的格式不同。私有属性会在属性名前加上%00类名%00,保护属性前加%00*%00。需要根据源码中的属性定义来调整Payload。本题中$flag是public,所以最简单。
3.开启错误显示:如果可能,在本地测试时开启display_errors,查看具体的PHP错误或警告信息。
返回“Welcome!”但无flag__destruct逻辑未按预期执行1.分析__destruct真实逻辑:我们之前审计的代码是简化的。真实题目的__destruct可能不是直接输出$flag,而是进行其他操作,比如调用其他对象的方法(POP链的起点)。需要更仔细地审计全部源码。
2.$flag属性值不对:可能flag文件的路径不是/flag,而是./flag、flag.txt或位于其他目录。需要结合题目描述、注释、其他接口进行路径猜测或遍历。
3.输出被过滤或重定向:__destruct中的输出可能被ob_start缓存,或者被后续代码覆盖。可以尝试将$flag的值设置为一个Web可访问的URL,让服务器发起请求(SSRF思路),或者写入一个文件。
Payload被WAF拦截存在安全防护1.混淆Payload:对序列化字符串进行多次编码(如Base64+URL编码)、添加无关字符(利用PHP反序列化特性,字符串长度后的冒号后可以有空格)、拆分参数等。
2.更改请求方式:尝试将Payload放在POST Body中传递。
3.寻找其他入口点:也许code参数不是唯一的反序列化点。

5.2 高级技巧与扩展思考

  1. 利用__destruct与__toString构建POP链:在更复杂的场景中,一个类的__destruct可能会调用另一个对象的某个方法,如果那个方法又触发了__toString或其他魔术方法,就可能形成一条“属性导向编程(POP)”链。审计时需要全局搜索所有类的魔术方法,寻找可以连接起来的“跳板”。
  2. 字符串逃逸与字符数量利用:这是另一种高级利用技巧。当序列化字符串在反序列化前经过了某些过滤函数(如str_replace)时,可能会因为字符数量的变化,导致序列化字符串的边界被“撑开”或“压缩”,从而使得后续部分被解析为新的属性,实现对象注入。这需要对序列化格式有极其精准的把握。
  3. Phar反序列化:一种更隐蔽的反序列化入口。如果网站存在文件上传功能,且可以上传Phar文件(或能通过修改文件头将其他文件伪装成Phar),并且有文件操作函数(如file_get_contents、include等)的参数可控,就可能触发Phar包中元数据(metadata)的反序列化。这是一种将反序列化与文件上传结合的综合利用方式。
  4. 关注PHP内置类:一些PHP内置类(如SimpleXMLElement、SoapClient、ArrayObject等)的魔术方法在某些情况下可以被利用来发起SSRF、发起请求或进行其他操作。在找不到自定义类利用链时,可以研究一下内置类。

5.3 防御措施建议(开发者视角)

既然我们作为攻击者研究了利用,那么从防御者角度,该如何避免此类漏洞呢?

  • 首要原则:不要反序列化不可信数据。这是最根本的。如果业务必须使用序列化,考虑使用JSON等更安全的格式。
  • 升级PHP版本:及时升级到已修复CVE-2016-7124的PHP版本。
  • 使用安全的白名单机制:如果必须使用unserialize,可以配合allowed_classes参数(PHP 7.0+),将其设置为false或一个明确的可信类名数组,只允许反序列化基础的、无害的类。
  • 对象签名与校验:在序列化数据中加入签名(HMAC),在反序列化前先验证数据的完整性和来源合法性。
  • 避免在魔术方法中放入关键逻辑:尤其是__wakeup、__destruct、__toString等,尽量不要在这些方法中执行文件操作、数据库查询、命令执行等敏感操作。如果必须,要严格检查对象属性的来源和有效性。
  • 代码审计与漏洞扫描:定期对代码进行安全审计,使用自动化工具扫描潜在的反序列化漏洞点。

回过头看unserialize3这道题,它像是一个精致的教学模型,把PHP反序列化漏洞中最经典的一个绕过场景单独提炼出来让我们练习。通过它,我们不仅学会了一个具体的绕过技巧(CVE-2016-7124),更重要的是建立起一套面对此类漏洞的通用分析方法:找入口、审代码、寻链子、构载荷、试绕过。在实际的渗透测试或CTF比赛中,情况会复杂得多,可能需要综合运用信息收集、代码审计、链式构造等多种能力。但万变不离其宗,对语言特性(这里是PHP魔术方法和序列化协议)的深刻理解,永远是解开这些谜题最可靠的钥匙。下次当你再遇到unserialize时,希望你能清晰地想起整个分析流程,从容地拆解它。

相关新闻

  • Qwen2.5-Omni-3B全模态架构解析:MOE如何驱动3B模型实现跨模态对齐
  • Ponytail:让AI Agent化身最懒的资深开发——代码暴砍54%,测试100%通过
  • Python decimal精确计算:避免float金钱运算误差

最新新闻

  • MINBERR线性求解器:实现O(1/k²)后向误差率的通用收敛算法
  • 2026年最新张家界市黄金回收白银回收铂金回收彩金回收靠谱门店TOP5权威榜单+实体老店联系方式 - 亦辰小黄鸭
  • 2026年6月口碑好的排烟防火阀供应商推荐,消防通风工程施工/车间除尘通风工程/通风工程,排烟防火阀厂商口碑推荐 - 品牌推荐师
  • 强化学习调优大语言模型,实现AI驱动的智能药物分子设计
  • CROSSMATH基准:诊断视觉语言模型在数学推理中的模态鸿沟
  • 2026年京东云 618 活动Hermes Agent/OpenClaw配置Token Plan详细方法汇总

日新闻

  • 2026速览惠州叛逆青少年学校前十大排名名单出炉 - 武汉中职最新信息发布
  • 2026上饶白蚁消杀哪家好?15年本土2大权威白蚁防治公司推荐(金盾虫控/青蚁卫士) - 我叫一
  • 天龙八部单机版终极数据管理工具:5个技巧快速掌握游戏数据编辑

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

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

关于尧图

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

服务项目

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

快速链接

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

联系方式

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

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