CTF实战:手把手教你用PHP弱比较和数组绕过BuyFlag题(附BurpSuite操作)
CTF实战PHP弱类型比较与数组绕过在BuyFlag题中的精妙应用引子从一道经典CTF题看PHP的安全陷阱去年某场线下CTF比赛中一道看似简单的BuyFlag题目难住了近60%的参赛者。当我看到队友反复尝试却始终无法获取flag时突然意识到这正是PHP类型系统特性的典型体现。这道题完美展示了为什么PHP会被戏称为世界上最好的语言——它的类型转换规则总能给安全人员带来惊喜。今天我们就以这道题为切入点深入剖析PHP弱比较()和strcmp数组绕过的底层机制。不同于简单的解题步骤罗列本文将带你从PHP解释器层面理解这些特性并掌握BurpSuite实战中的高阶操作技巧。无论你是刚接触Web安全的萌新还是想深化PHP代码审计能力的进阶者都能从中获得可迁移的实战经验。1. 题目环境与初步代码审计首先让我们还原题目场景一个购买flag的页面需要满足三个条件必须是学生用户Cookie: user1输入正确的password非数字且弱等于404支付足够的money100000000元查看页面源码会发现关键验证逻辑if (isset($_POST[password])) { $password $_POST[password]; if (is_numeric($password)) { echo password cant be number; } elseif ($password 404) { echo Password Right!; } }这里出现了第一个安全关键点is_numeric检查与弱比较的组合使用。这种组合在CTF和实际审计中极为常见却暗藏玄机。1.1 is_numeric的检测边界该函数不仅检测纯数字还会检测以下格式科学计数法如1e3十六进制如0x1A带正负号的数字如404但有个重要例外尾部包含非数字字符的字符串。例如404a → false404! → false404 → false注意末尾空格提示在BurpSuite测试时可以使用Intruder模块批量测试各种边界值快速定位有效payload。1.2 PHP弱比较的隐式转换当password通过is_numeric检查后进入 404判断。PHP的弱比较会尝试将两边值转换为相同类型后再比较。转换规则复杂但有几个关键点比较类型转换规则示例典型绕过payload字符串 vs 数字取字符串前导数字部分404a字符串 vs 布尔值非空字符串 trueon true数组 vs 任何类型数组永远大于其他类型[] 0 → false在本题中404a 404的转换过程字符串404a与数字404比较PHP取字符串前导数字部分得到404404 404成立2. BurpSuite实战从基础操作到高阶技巧2.1 初始请求捕获与修改使用BurpSuite的基本流程浏览器配置代理推荐FirefoxFoxyProxy拦截原始GET请求右键发送到Repeater修改请求方法为POST并添加参数POST /buyflag.php HTTP/1.1 ... Cookie: user0 password404amoney100000000常见新手错误直接在Proxy里修改GET为POST部分服务器仍视为GET忘记修改Content-Length头部URL编码处理不当空格需转为%202.2 Cookie权限绕过观察到响应头提示仅学生用户可以购买FLAG检查Cookie发现Cookie: user0PHP中常见权限验证逻辑if ($_COOKIE[user]) { // 授权逻辑 }这里存在两个潜在问题弱类型判断任何非零值都可能通过缺乏签名验证Cookie可被随意修改解决方案修改为user1测试其他真值如usertrue2.3 科学计数法绕过长度限制当提交money100000000时返回Number length is too long。尝试科学计数法1e8 → 1000000001e9 → 1000000000十六进制0x5f5e100 → 100000000字符串特殊格式100000000带引号1e8字符串形式测试发现1e9能绕过长度检查但提示金额不足说明存在数值比较if ($money 100000000) { // 发放flag }3. 深入strcmp的数组绕过技术当money参数继续调整时我们遇到更复杂的过滤。推测后端使用了strcmp函数if (strcmp($_POST[money], $flag_price) 0) { echo $real_flag; }3.1 strcmp函数的行为特性这个函数设计用于比较两个字符串但当传入非字符串参数时传入数组 → 返回NULLNULL 0 → true弱比较实现绕过构造payloadmoney[]1相当于创建$_POST[money]为数组使strcmp返回NULL。3.2 不同PHP版本的差异注意这种绕过方式在不同PHP版本的表现PHP版本strcmp(数组,字符串)弱比较NULL05.x返回NULLtrue7.x产生Warning但仍返回NULLtrue8.0抛出TypeError异常不适用实际CTF比赛中PHP5环境占多数这种方法仍然有效。但在现代PHP开发中应该使用严格比较和类型检查。4. 防御方案与安全编程实践4.1 安全的比较方式不安全写法安全替代方案$a $b$a $bstrcmp($a,$b) 0strcmp($a,$b) 0is_numeric($input)ctype_digit($input)4.2 输入验证最佳实践// 严格类型检查 if (isset($_POST[password]) is_string($_POST[password])) { // 处理逻辑 } // 使用filter_var校验 $money filter_input(INPUT_POST, money, FILTER_VALIDATE_FLOAT); if ($money false || $money 100000000) { die(Invalid money); } // 白名单校验 $allowed [404, 404a, 404b]; if (!in_array($_POST[password], $allowed, true)) { die(Invalid password); }4.3 BurpSuite工作流优化Macro录制对重复操作如修改Cookie创建宏Logger扩展记录所有请求响应便于回溯Turbo Intruder处理大量Payload测试Collaborator检测盲注类漏洞# 使用Turbo Intruder的示例基础模板 def queueRequests(target, wordlists): engine RequestEngine(endpointtarget.endpoint, concurrentConnections5, requestsPerConnection100, pipelineFalse) for i in range(100): engine.queue(target.req, str(i)) def handleResponse(req, interesting): if Password Right in req.response: table.add(req)5. 从CTF到实战PHP类型漏洞的延伸思考去年某次渗透测试中我们发现一个后台管理系统使用类似的弱比较验证管理员权限if ($_SESSION[is_admin] true) { // 显示管理功能 }通过修改Cookie为is_admin1而非布尔值true我们成功绕过验证。这种模式在老旧PHP系统中非常普遍。另一个真实案例是支付系统的金额验证if ($_POST[amount] 0) { process_payment(); }攻击者传入amount1e-999由于PHP会将其转换为0导致支付绕过。防御方法是使用is_int()或filter_var()严格校验。在BurpSuite的实战使用中我发现2020版之后Repeater模块有时会出现卡顿。临时解决方案是禁用不必要的扩展调整内存设置burp.properties使用旧版如2021.8处理复杂操作这些经验告诉我们无论是CTF还是真实环境理解PHP的类型系统特性都至关重要。建议每个安全人员都深入阅读PHP官方文档中的类型比较表并在BurpSuite中建立自己的测试用例库。