PHP反序列化漏洞入门:从ez_unserialize到POP链构造实战
最近翻到一道很有意思的新生赛题[SWPUCTF 2021 新生赛]ez_unserialize名字里那个ez很诚实但仔细做下来你会发现这题把PHP反序列化漏洞里最核心的几条知识点全都串了一遍。我一直觉得反序列化漏洞是Web安全里非常适合新手入门的方向它不像堆溢出、条件竞争那样需要很深的底层功底关键在于理解PHP对象生命周期和魔术方法的调用机制而这道题正好是这条学习路径上一个不错的节点。今天这篇就从头到尾复盘一下这道题的完整思考和解法包括PHP序列化机制的底层逻辑、代码审计时怎么高效率定位可利用点、payload手工构造的全过程以及新生赛里常见的干扰项处理。不管你是刚接触CTF的新人还是想把反序列化基础打牢一点这篇都值得花几分钟看完。1. 反序列化漏洞的底层原理从serialize到unserialize先说清楚PHP的序列化机制。序列化serialize就是把一个对象转换成一串带有类型标记和属性名、属性值的字符串方便存储或传输反序列化unserialize则是把这串字符重新还原成内存里的PHP对象。单看机制本身这功能很正常问题出在反序列化过程中PHP会自动触发对象里的某些魔术方法。PHP里有一组以双下划线开头的方法统称魔术方法它们不需要你手动调用而是在特定事件发生时自动执行。和反序列化直接相关的魔术方法主要有这么几个方法名触发时机典型用途__construct()对象被实例化时初始化属性__destruct()对象被销毁时脚本结束或unset释放资源__wakeup()反序列化恢复对象时重新初始化连接等__toString()对象被当作字符串使用时返回字符串表示__get($name)访问不可访问或不存在的属性时动态属性读取真正的危险点在于如果反序列化的数据来自用户可控输入而某个类的魔法方法里有敏感操作比如读写文件、执行命令、拼SQL那么攻击者只要精心构造序列化字符串就能在反序列化的一瞬间或脚本收尾阶段让这些危险操作被自动触发。用个生活化的类比序列化就像你把行李箱打包每个物品外面贴上标签写明这是什么、里面装着什么反序列化就是拆箱而拆箱过程中箱子自己会弹开几个暗格——如果这个箱子被设计成暗格里藏了个自动发夹子机关那你打开箱子时就会被夹到。攻击者做的事就是往箱子里塞一张精心裁剪过的纸条让拆箱过程把隐藏的机关触发出来。在CTF里反序列化题目最常见的形态就是代码把用户输入直接丢给unserialize()处理而代码中某个类的某个魔术方法里存在可利用的敏感函数。ez_unserialize就是这种典型结构。2. 源码审计的思路三分钟定位可利用点拿到题目首先看到的是源码这种新生赛反序列化题基本都会把代码直接贴出来。审计代码时我习惯按固定顺序来效率比较高。先找unserialize()的调用位置和入参来源确认是否有过滤。再列出所有定义了魔术方法的类尤其是__destruct、__wakeup、__toString、__get这些反序列化过程会触发的。最后看这些魔术方法内部的函数调用优先关注文件操作、命令执行、SQL拼接三类危险函数。这道题的核心源码大致可以简化为这样一个结构?php class A { public $info; public function __destruct() { echo $this-info; } } $data $_GET[data]; if (isset($data)) { unserialize($data); }注意到几个关键信息第一unserialize()的入参直接来自$_GET[data]完全没有任何过滤这是典型的可控输入点。第二A类定义了__destruct()析构函数它做的事情是echo $this-info。第三对象被反序列化出来后如果没有被显式销毁会在脚本执行结束时自动调用析构函数。那问题来了echo一个对象属性的值看起来很人畜无害怎么利用关键就在这里。反序列化时攻击者能完全控制对象的所有属性值。如果我们把$info这个属性的值设置成某个对象一个实现了__toString()方法的对象那么echo $this-info在尝试输出这个对象时PHP就会自动调用这个对象的__toString()方法。这一步就构成了反序列化漏洞利用里非常经典的POP链雏形。所谓POP链就是面向属性编程Property-Oriented Programming核心思想是通过控制对象的属性值让代码原有的函数调用关系为我们所用让一个无害的操作触发一串危险的执行链。不需要注入新代码只需要精心搭配已有的类和属性。明白了这一点你就会理解为什么反序列化题目的审计重点不是找到直接命令执行点而是梳理类与类之间的调用链条——安全研究员常说的gadget就是这么串联起来的。3. 从触发点反推一个有意思的魔术方法设计第一步审计发现了出口__destruct里的echo至少能把一个对象变成字符串。接下来要做的就是找到代码里哪个类实现了__toString方法并且这个方法内部有敏感操作。这类题里最常见的出法有两种一种是目标类有一个方法会读取文件内容比如file_get_contents而__toString直接返回这个读取结果另一种是目标类的__toString方法里直接调用了危险函数比如eval、system、call_user_func等。本次题目属于前者。假设源码里还有这样一个类?php class B { public $filename; public function __toString() { return file_get_contents($this-filename); } }逻辑链条立即清晰了反序列化入口$_GET[data]传给unserialize()。反序列化还原一个A类对象其$info属性被设置成一个B类对象。脚本结束A类对象析构执行echo $this-info。echo一个B类对象触发B::__toString()。B::__toString()执行file_get_contents($this-filename)。文件内容被当作字符串输出到页面。所以payload要达成的目标是构造一个A对象它的$info属性指向一个B对象且这个B对象的$filename属性值是目标敏感文件路径。现在整个利用链完全清晰剩下的就是手工生成对应的序列化字符串。这也是手工构造的一个好处比用网上工具更稳同时能加深对格式的记忆题目里稍微改改结构你也能反应过来。4. 序列化字符串手工构造一步一步推导出payload先说PHP序列化格式的基本规则新手容易在这里绕晕。一个对象序列化后是这样一段字符O:1:A:1:{s:4:info;O:1:B:1:{s:8:filename;s:5:flag.php;}}拆解这段字符每个部分含义如下片段含义O:1:A反序列化一个对象类名长度为1类名为A:1:该对象有1个属性{ ... }属性列表s:4:info属性名字符串类型长度4内容是infoO:1:B:...info属性的值是一个B类对象s:8:filenameB对象的属性名filenames:5:flag.phpfilename属性的值字符串flag.php字符串用s:长度:内容表示对象用O:长度:类名:属性数:{...}表示读起来很像一串带元数据的协议。只要记住这几个基本类型几乎所有简单反序列化payload都可以手写。有个容易踩坑的细节属性名的长度必须准确。比如info是4个字符你写s:3:info就会导致反序列化失败或者解析偏移拿到意想不到的结果。CTF里只要格式有一点点错unserialize都会返回false然后题目的输出就什么都没了。真出问题时第一步永远是检查长度。现在根据题目目标构造完整的payload。假设目标文件是根目录下的flag.php?php class A { public $info; } class B { public $filename; } $a new A(); $a-info new B(); $a-info-filename flag.php; echo serialize($a);输出结果O:1:A:1:{s:4:info;O:1:B:1:{s:8:filename;s:8:flag.php;}}看到差别没有flag.php长度是8不是5上面举例时我故意写的是s:5:flag.php那种payload直接反序列化失败。这种低级错误我当年也犯过排查了很久才反应过来是字符串长度和实际内容不匹配。现在把payload放到GET参数里提交?dataO:1:A:1:{s:4:info;O:1:B:1:{s:8:filename;s:8:flag.php;}}这里还隐藏着一个新生赛专门用来考验选手的坑序列化字符串里的引号、花括号、双引号在URL里属于特殊字符如果你直接复制进浏览器地址栏或者用curl直接传会被URL解析破坏掉服务端收到的data参数值早就不完整了unserialize自然失败。解决办法是先用URL编码。常见需要编码的字符有双引号编码为%22花括号{编码为%7B}编码为%7D分号;不需要编码但保险起见也可以编。这个payload编码后长这样?dataO%3A1%3A%22A%22%3A1%3A%7Bs%3A4%3A%22info%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D个人习惯不管参数多简单提交payload永远走URL编码养成肌肉记忆对后面做更复杂的题目帮助很大。用curl验证很方便curl http://目标地址/题目路径/?dataO%3A1%3A%22A%22%3A1%3A%7Bs%3A4%3A%22info%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D如果一切顺利响应里就是flag.php的内容。5. 常见干扰项与绕过手法为什么你的payload打不出来做这道题过程中最容易遇到的坑也代表了一类反序列化新手题的干扰套路。第一个干扰项是URL编码问题前面已经讲了。很多人在这一步踩坑以为payload错了其实是传输层被URL解析修改了。第二个干扰项是某个类里存在__wakeup()方法。前面说过__wakeup在反序列化时自动触发比__destruct更早。如果目标类里的__wakeup做了某些破坏操作比如把$filename置空或者直接exit你的POP链就断在起跑线上了。经典绕过手段是CVE-2016-7124当反序列化字符串里对象的属性数量大于真实属性数量时PHP会跳过__wakeup()的执行。具体来说把对象属性个数的那个数字改大比如原本是1改成2或更大。这个漏洞在新版PHP里已经被修复但很多CTF题目为了考察知识点会用旧版本环境搭建所以仍然是一个需要掌握的技巧。第三个干扰项是序列化字符串里属性名长度不一致这个前面已经讲过了但值得再说一次手工构造payload非常容易出现长度计算错误尤其是属性名是中文或特殊字符的时候。第四个干扰项是反序列化后对象在脚本结束前被显式销毁__destruct提前触发导致输出顺序变化。有些题目里会写unset($obj)或者把对象丢进数组再释放导致你预期的输出位置变了。这种干扰在新生赛里出现概率不高但遇到过几次解决方案是在本地用PHP写个Demo把题目源码简化后跑一遍观察输出。为了方便排查我列一张自查清单每当payload打不通就按这个顺序检查检查项操作value是否被过滤查看是否有waf或正则替换特殊字符URL编码是否完整将payload全部做百分号编码序列化格式是否合法用php的serialize()生成标准payload做对比魔术方法触发顺序本地搭建环境逐步echo调试目标文件路径是否正确尝试读取index.php、/etc/passwd等路径6. 一类通用规律的提炼反序列化题的标准解题路径把ez_unserialize做完可以顺手总结出一套适合PHP反序列化新手题的通用解题路径。这套路径我后来用在了不少其他题目上命中率很高这里分享给大家。第一步找入口。全局搜索unserialize、$_GET、$_POST、$_REQUEST这些关键词。注意有些题目的入口藏在比较深的位置比如先经过一个json_decode或者先经过自定义的解析函数再回到unserialize。第二步找危险函数。把代码里所有命令执行类system、exec、shell_exec、eval、文件读写类file_get_contents、file_put_contents、fopen、include、数据库操作类函数全部标出来这个就是攻击终点。第三步找魔法方法。看这些危险函数被哪个方法包裹尤其是__destruct、__wakeup、__toString、__get、__call、__isset、__set。反序列化的魔法方法触发时机不同利用链入口就不同__destruct对象销毁时触发最常出现因为脚本结束必然销毁。__wakeup反序列化时最先触发也常被用来做过滤或初始化。__toString对象作为字符串使用时的自动转换适合做链中衔接。__get/__call访问不存在属性或方法时触发配合extract、动态调用时很好用。第四步从终点倒推入点把POP链串起来。拿危险函数所在的类作为链尾检查有哪些魔法方法会调用它然后控制它所在类的属性值继续往前推直到找到一个能接收用户输入的反序列化入口类。第五步用PHP本地生成payload。这是很重要的一个质量保障本地可以用真实的类定义跑保证序列化字符串里的每个长度都准确无误。你当然可以直接在浏览器里手写但一旦类名或属性名有字母大小写差别本地一跑就知道。我在实际做题时习惯把题目给出的源码复制到本地写上生成payload的PHP脚本在命令行里跑出来再交给curl提交。这个流程比手写安全很多也方便批量尝试不同路径。用本地脚本还有一个隐藏优势你可以直接在类里加调试代码比如每个魔术方法里加一个echo标记这样就能精确知道反序列化过程到底触发了哪些方法、执行顺序是什么。遇到复杂的链时这招几乎是半开卷做题。7. 入门之后的延伸思考ez_unserialize这道题本身不算难但它非常适合作为理解反序列化漏洞的起点。做完之后有两条知识线值得继续深入。第一条线是POP链的复杂度升级。新生赛通常只有一个跳板类从A到B但真实场景的题目可能有五六个类互相引用甚至有数组、对象嵌套这时候手工构造就不现实了。我看到很多成熟的选手会写Gadget链自动生成脚本扫描类之间的关系搜索一条从入口到危险函数的路径。早期自己手工搭链的意义在于理解每一步的触发原因而不是追求能搭多复杂的链。第二条线是真正开发场景下的反序列化防护。多数框架已经把反序列化入口收得很紧但历史上有大量出自知名框架的反序列化漏洞。研究这类漏洞的乐趣在于它暴露的是框架设计者在灵活和安全之间的取舍失误理解这种取舍对未来做代码审计或写安全代码都有帮助。还有一点值得留个心眼凡是会拿到外来数据做反序列化的地方都应该使用白名单机制限制允许的类名或者改用JSON等格式从根本上降低风险。CTF里我们研究怎么打穿实际工程里要想的是怎么防住。安全研究的两头都很重要。回到这道题本身我最终拿到flag的时候回头看整个利用链只用了两个类、一个析构函数、一个字符串转换方法、一条文件读取操作。新生赛ez的含义确实名不虚传但恰恰是这种简单题目把反序列化里最核心的思路完整呈现了出来。弄清这条链在代码里是怎么走通的后面遇到任何反序列化题你都会有一种原来是变了个壳的通透感。

相关新闻

批量转双层PDF:扫描件如何获得可搜索的隐形文本层

批量转双层PDF:扫描件如何获得可搜索的隐形文本层

简介:这款批量转双层PDF工具v1.0,面向档案数字化、办公文档管理和需要检索扫描件的用户,能批量识别文件夹内的PDF并生成包含图像层与文本层的双层PDF,解决扫描文件不能搜索、复制和索引的痛点。工具基于PaddleOCR模型打造&#xf…

2026/10/13 7:40:10 阅读更多 →
C++塔防课程设计实战:基于Qt的工程结构、核心逻辑与避坑指南

C++塔防课程设计实战:基于Qt的工程结构、核心逻辑与避坑指南

简介:基于C实现的保卫萝卜塔防游戏课程设计源码包,面向具备C基础、希望用Qt完成游戏大作业的在校生或游戏开发初学者。项目完整还原经典塔防玩法:三个关卡、五种怪物、四种防御塔,英雄拥有6条命和初始1000金币,通过消灭…

2026/10/13 7:39:09 阅读更多 →
Python安全除法实战:从ZeroDivisionError到numpy向量化

Python安全除法实战:从ZeroDivisionError到numpy向量化

1. 从一次线上事故说起:为什么我放弃裸除先讲一件真事。某次我在负责一个实时数据处理模块,核心逻辑里有一行不起眼的除法运算。线上环境某天凌晨涌进来一批异常数据,分母恰好为 0,结果整个服务直接崩了。等我从睡梦中被报警电话叫…

2026/10/13 7:39:09 阅读更多 →

最新新闻

Schema标记实战:用结构化数据占据整个SERP

Schema标记实战:用结构化数据占据整个SERP

1. "占据整个SERP"到底是怎么回事:从一次搜索结果截图说起先别急着上代码,我们得先搞清楚一个问题:所谓"占据整个SERP",到底长什么样?去年我接手了一个垂直电商的小项目,卖的是某种小众…

2026/10/13 8:16:34 阅读更多 →
AutoTool实操:Agent工具路由如何省下30%推理成本

AutoTool实操:Agent工具路由如何省下30%推理成本

AAAI 2026 放榜之后,我朋友圈里最热闹的话题除了 Jev 这种突然爆火的开源 Agent 项目,就是各路团队在吐槽“推理成本根本扛不住”。Jev 确实把复杂任务的拆解能力拉到了一个新高度,但问题也随之而来:工具越挂越多、调用链越拉越长…

2026/10/13 8:16:34 阅读更多 →
免费磁盘清理工具 Czkawka:找重复文件与清理空文件夹完整上手指南

免费磁盘清理工具 Czkawka:找重复文件与清理空文件夹完整上手指南

免费磁盘清理工具 Czkawka:找重复文件与清理空文件夹完整上手指南 【免费下载链接】czkawka Multi functional app to find duplicates, empty folders, similar images etc. 项目地址: https://gitcode.com/GitHub_Trending/cz/czkawka Czkawka 是一款用 Ru…

2026/10/13 8:16:34 阅读更多 →
Faker库实战:让接口测试与自动化测试的造数更真实高效

Faker库实战:让接口测试与自动化测试的造数更真实高效

做接口测试和自动化测试的朋友,应该都体验过那种被“假数据”支配的尴尬。我早年在某项目组做下单流程的接口测试,为了凑测试用户,手工在配置文件里写了几百个“张三”,手机号清一色13800138000,地址全是“北京市朝阳区…

2026/10/13 8:16:34 阅读更多 →
带实习生三个月,我总结了一套 Mentor 带教方法:从前端页面切入到后端接口拆解(附完整清单)

带实习生三个月,我总结了一套 Mentor 带教方法:从前端页面切入到后端接口拆解(附完整清单)

文档教程知识库 【免费下载链接】InterviewGuide 🔥🔥「InterviewGuide」是阿秀从校园->职场多年计算机自学过程的记录以及学弟学妹们计算机校招&秋招经验总结文章的汇总,包括但不限于C/C 、Golang、JavaScript、Vue、操作系统、数据结…

2026/10/13 8:16:34 阅读更多 →
CyFA:放弃预测残差,用循环特征对齐提升多变量时间序列异常检测

CyFA:放弃预测残差,用循环特征对齐提升多变量时间序列异常检测

多变量时间序列异常检测这块,我前前后后折腾了不少方案。最早用的也是"先预测、再算残差"的老路子,后来复现了 GDN 和 KDA 这两个基线,说实话越用越觉得别扭。直到我自己折腾出一个叫 CyFA 的思路,直接扔掉 Delta Rule&…

2026/10/13 8:15:34 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →