CTF刷题笔记:我在解ctfshow-web时踩过的那些‘弱类型’坑(PHP md5与session篇)
CTF刷题笔记:PHP弱类型比较的陷阱与实战解析
第一次接触ctfshow的web5题目时,我盯着那个要求两个不同字符串MD5值相等的条件愣了足足五分钟。这怎么可能?直到我输入"QNKCDZO"和"240610708"这两个看似毫无关联的字符串后,系统竟然返回了flag——那一刻,我意识到自己遇到了PHP语言最著名的特性之一:弱类型比较漏洞。
1. PHP类型系统的双面性
PHP作为动态类型语言,其灵活的类型转换机制是把双刃剑。在CTF比赛中,这种特性常常成为解题的关键突破口。让我们先从一个基础但至关重要的概念开始: == 与 === 的区别。
==(松散比较):在比较前会尝试类型转换===(严格比较):要求值和类型完全一致
var_dump("123abc" == 123); // true
var_dump("0e123" == "0e456"); // true
var_dump(false == ""); // true
这些看起来"反直觉"的结果,正是许多CTF题目的设计基础。特别是在处理用户输入时,如果开发者错误地使用了 == 而非 === ,就可能引入安全漏洞。
2. MD5碰撞的魔法:0e开头的秘密
回到让我困惑的web5题目,它的核心逻辑是:
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])){
echo $flag;
}
这里有几个关键点需要注意:
- 输入的两个参数必须不同
- 它们的MD5值在松散比较下相等
- MD5哈希通常被认为是唯一的(虽然实际存在碰撞可能)
解题的突破口在于PHP对科学计数法的特殊处理。当字符串以"0e"开头,后面全是数字时,PHP会将其视为科学计数法表示的零:
| 原始字符串 | MD5哈希值 | PHP松散比较结果 |
|---|---|---|
| QNKCDZO | 0e8304004519939 | 0 |
| 240610708 | 0e4620974319065 | 0 |
提示:这类字符串被称为"魔术哈希"(Magic Hash),在密码校验等场景也可能造成安全问题。
3. Session处理的陷阱:空值的玄机
web11题目展示了另一种弱类型比较的利用方式——通过Session机制。题目逻辑大致如下:
if($_POST['password'] == $_SESSION['password']){
// 授权通过
}
当遇到这类题目时,可以尝试以下方法:
- 清空Cookie中的PHPSESSID
- 提交空密码
这样做的原理是:
- 清空PHPSESSID会导致服务器无法找到对应Session
- 未找到的Session返回null
- 空密码在比较时也会被转换为null
- null == null 成立
// 模拟服务器端处理
$_SESSION = []; // 空Session
$inputPwd = "";
var_dump($inputPwd == $_SESSION['password']); // true
4. 防御之道:从CTF到实战
理解了这些漏洞原理后,更重要的是如何在真实开发中避免类似问题。以下是一些实用建议:
代码审计时的危险信号:
- 使用
==进行敏感数据比较(密码、权限校验等) - 直接使用
md5()等哈希函数而不加盐 - 未初始化Session变量就直接使用
安全编码实践:
-
始终使用严格比较 :
// 不安全 if($userInput == $expectedValue) // 安全 if($userInput === $expectedValue) -
密码哈希最佳实践 :
// 不安全 $storedHash = md5($password); // 安全 $storedHash = password_hash($password, PASSWORD_DEFAULT); -
Session处理规范 :
// 不安全:直接比较 if($_POST['pwd'] == $_SESSION['pwd']) // 安全:初始化并严格比较 $_SESSION['pwd'] = $_SESSION['pwd'] ?? ''; if($_POST['pwd'] === $_SESSION['pwd'])
在CTF比赛中,这些"特性"是有趣的挑战;但在真实项目中,它们可能成为严重的安全隐患。记得在一次实际代码审计中,我发现某系统使用 in_array() 进行权限检查时没有启用严格模式,导致 0 == "admin" 返回true,最终实现了垂直越权。
更多推荐

所有评论(0)