一、核心基础:== 与 === 的本质区别
这是所有弱类型缺陷的根源,审计时首先要关注所有比较操作符的使用场景:
典型审计案例:
// 审计发现:登录校验用==,可被绕过if($_POST['password'] == $admin_pwd) { // 攻击者传0/0e123等可绕过 $_SESSION['admin'] = true;}
二、细分缺陷场景(含审计要点 + 案例)
1. MD5 比较缺陷(0e 开头的 “魔术字符串”)
原理:MD5 哈希值以0e开头时,PHP 会将其视为科学计数法(0 的任意次方仍为 0),使用==比较时,所有0exxxx格式的 MD5 值都会被判定为相等。
审计特征:代码中存在 md5($a) == md5($b) 或 md5($input) == $固定值。
漏洞案例:
// 审计发现:MD5比较用==,存在绕过风险$token = $_GET['token'];if(md5($token) == "0e123456789012345678901234567890") { echo "验证通过";}// 攻击者只需传:token=QNKCDZO(md5(QNKCDZO)=0e830400451993494058024219903391)// 所有0e开头的MD5值都会被==判定为相等
补充:常见的 0e 开头 MD5 “魔术字符串”:
QNKCDZO → 0e830400451993494058024219903391
240610708 → 0e462097431906509019562988736854
s878926199a → 0e545993274517709034328855841020
2. strcmp () 函数类型比较缺陷
原理:strcmp($a, $b) 设计用于比较字符串,但:
若传入非字符串类型(如数组),函数返回null;
null == 0(松散比较),导致校验被绕过。
审计特征:代码中存在 strcmp(用户输入, 固定值) == 0,且未校验输入类型。
漏洞案例:
// 审计发现:strcmp比较无类型校验$pwd = $_GET['pwd'];if(strcmp($pwd, $correct_pwd) == 0) { // 攻击者传?pwd[]=任意值 echo "登录成功"; // strcmp返回null,null==0为true}
3. Bool 类型比较缺陷
原理:PHP 中非布尔值会隐式转为布尔值:
空字符串""、0、null、空数组[]、false → 转为false;
非空字符串、非 0 数字、非空数组 → 转为true。松散比较时易被绕过。
审计特征:用==比较布尔值(如 $input == true),或直接将用户输入作为布尔判断条件。
漏洞案例:
// 审计发现:布尔比较逻辑缺陷$is_admin = $_GET['admin'];if($is_admin == true) { // 攻击者传?admin=123(非0数字→true)或?admin=任意字符串 echo "管理员权限";}// 另一个场景:空数组绕过$check = $_GET['check'];if($check == false) { // 传?check[]=1 → 空数组?不,非空数组→true;传?check=0 或 ?check= → false echo "校验通过";}
4. switch 类型比较缺陷
原理:switch 内部使用松散比较(==) 匹配case,而非严格比较,会触发隐式类型转换。
审计特征:switch(用户输入) 搭配case 数字/字符串,无类型校验。
漏洞案例:
php
// 审计发现:switch松散比较绕过$action = $_GET['action'];switch($action) { case 1: // 攻击者传?action=1abc → 转为1,匹配成功 echo "执行管理员操作"; break; case 2: echo "执行普通操作"; break;}
5. in_array 数组比较缺陷
原理:in_array($needle, $haystack, $strict) 第三个参数默认false(松散比较),用户输入会被隐式转换后匹配数组值。
审计特征:in_array(用户输入, 白名单数组) 未开启严格模式。
漏洞案例:
// 审计发现:in_array无严格模式$allowed_roles = [1, 2, 3]; // 1=管理员,2=普通用户,3=访客$user_role = $_GET['role'];if(in_array($user_role, $allowed_roles)) { // 传?role=1abc → 转为1,匹配成功 echo "角色合法,执行操作";}
6. === 数组比较缺陷(MD5 返回 NULL)
原理:
md5() 仅接受字符串 / 数字类型,若传入数组,会返回null;
用===比较两个null(如md5($arr1) === md5($arr2))会返回true,导致校验失效。
审计特征:md5(用户输入) === 固定MD5值,但未校验输入是否为数组。
漏洞案例:
// 审计发现:MD5+===但未校验输入类型$token = $_GET['token'];$correct_md5 = "e10adc3949ba59abbe56e057f20f883e"; // md5(123456)if (md5($token) === $correct_md5) { // 传?token[]=1 → md5($token)=null,与$correct_md5不相等? // 修正案例:攻击者传两个数组,md5($arr1)=null,md5($arr2)=null,===会相等 $a = $_GET['a']; $b = $_GET['b']; if (md5($a) === md5($b)) { // 传?a[]=1&b[]=2 → 两个md5都返回null,===为true echo "校验通过"; }}
三、补充其他高频比较缺陷(审计易遗漏)
1. array_key_exists () 隐式转换缺陷
原理:array_key_exists($key, $arr) 对数字字符串键名会隐式转换(如键名123和字符串"123"视为同一个键)。
审计特征:array_key_exists(用户输入, 权限数组) 用于权限控制。
案例:
$admin_keys = [100 => 'admin', 200 => 'user'];$key = $_GET['key'];if(array_key_exists($key, $admin_keys)) { // 传?key=100abc → 转为100,匹配成功 echo "有权限";}
2. intval () 函数的截断缺陷
原理:intval($str) 会截取字符串开头的数字部分(如intval("100abc")=100),松散比较时易被利用。
审计特征:intval(用户输入) == 固定值 用于 ID / 权限校验。
案例:
$admin_id = 100;$user_id = intval($_GET['id']);if ($user_id == $admin_id) { // 传?id=100abc → intval=100,匹配成功 echo "管理员权限";}
3. strpos () 函数的 0 值混淆缺陷
原理:strpos($str, $sub) 查找子串位置,返回:
数字(位置)或false(未找到);
0(子串在开头)与false松散比较(0 == false)会被误判为未找到。
审计特征:strpos(用户输入, 关键字) == false 用于内容校验。
案例:
// 审计发现:strpos比较用==,导致开头匹配被误判$content = $_GET['content'];if(strpos($content, '危险关键词') == false) { // 传content=危险关键词123 → strpos返回0,0==false为true echo "内容合法"; // 实际包含危险关键词,却被放行}// 正确写法:strpos(...) === false
4. empty () 函数的类型缺陷
原理:empty($var) 会判断多种 “空值”,包括:""、0、0.0、"0"、null、[]、false,松散的判断逻辑易被绕过。
审计特征:empty(用户输入) 用于必填参数校验。
案例:
// 审计发现:empty校验手机号,0开头号码被误判$phone = $_GET['phone'];if (empty($phone)) { // 传phone=0123456789 → "0123456789"非空,但传phone=0 → empty(0)=true echo "手机号不能为空";}// 正确写法:!isset($phone) || $phone === ""
四、审计修复通用方案
针对所有弱类型 / 比较缺陷,审计时需督促开发遵循以下规则:
强制严格比较:安全场景(登录、权限、校验)必须用===/!==,杜绝==/!=;
前置类型校验:对用户输入先校验类型(is_string()/is_int()/is_array()),再处理;
显式类型转换:关键参数手动转换为目标类型(如$id = (int)$_GET['id']);
开启函数严格模式:in_array()/array_search() 必须加第三个参数true;
避免直接用用户输入作为判断条件:布尔判断前先做值 + 类型校验;
特殊函数加固:
strcmp():先校验is_string(),再用=== 0比较;
strpos():用=== false判断是否未找到;
md5():先校验输入为字符串,再比较。