web安全代码基础-PHP(函数-特性安全)
PHP的弱比较弱相等松散比较先把两边值强制转换成相同类型再比较值是否相等会自动类型转换容易出现反直觉结果。全等严格比较先判断类型是否完全一致类型不一样直接返回false类型相同再比较值不会自动转类型推荐日常使用。?php // 1. 数字和字符串对比 $num 123; $str 123; echo 弱比较 . var_export($num $str, true) . PHP_EOL; // true 自动转成数字比较 echo 强比较 . var_export($num $str, true) . PHP_EOL; // false 类型不同 int vs string echo PHP_EOL; // 2. 0 和字符串、布尔值 echo 0 abc . var_export(0 abc, true) . PHP_EOL; // true 字符串转数字是0 echo 0 abc . var_export(0 abc, true) . PHP_EOL; // false echo PHP_EOL; // 3. true/false 和数字 echo true 1 . var_export(true 1, true) . PHP_EOL; // true echo true 1 . var_export(true 1, true) . PHP_EOL; // false echo false 0 . var_export(false 0, true) . PHP_EOL; // true echo false 0 . var_export(false 0, true) . PHP_EOL; // false echo PHP_EOL; // 4. null、false、空字符串 echo null false . var_export(null false, true) . PHP_EOL; // false echo null . var_export(null , true) . PHP_EOL; // false echo null 0 . var_export(null 0, true) . PHP_EOL; // false echo null false . var_export(null false, true) . PHP_EOL; // false echo PHP_EOL; // 5. 空数组、null、false $emptyArr []; echo [] false . var_export($emptyArr false, true) . PHP_EOL; // true echo [] false . var_export($emptyArr false, true) . PHP_EOL; // false echo [] null . var_export($emptyArr null, true) . PHP_EOL; // true echo [] null . var_export($emptyArr null, true) . PHP_EOL; // false echo PHP_EOL; // 6. 字符串数字和数字带字母 echo 123 123abc . var_export(123 123abc, true) . PHP_EOL; // true字符串截取数字部分123 echo 123 123abc . var_export(123 123abc, true) . PHP_EOL; // false ?补var_export()函数用于输出或返回一个变量以字符串形式表示。mixed var_export ( mixed $expression [, bool $return ] ) //$expression: 你要输出的变量。 //$return: 可选如果设置为 TRUE该函数不会执行输出结果而且将输出结果返回给一个变量。另外布尔值true在参与数值比较时会转换为整数1false会转换为整数0。特殊规则当比较null与数字时PHP 认为null不等于任何数字包括0null只与null和未定义的变量相等。实战实例场景 1验证码 / 密码校验漏洞?php $input 123abc; $code 123; if ($input $code) { echo 弱比较通过校验失效; // 会输出不安全 } if ($input $code) { echo 强比较通过; // 不会执行安全 } ?场景2判断是否等于字符串0?php $val 0; //这里的0为数字型下面的0为字符型 if ($val 0) { echo 弱比较相等; // true } if ($val 0) { echo 强比较相等; // false } ?修复建议1、业务判断一律用、!避免隐式类型转换带来逻辑 bug2、仅在你明确知道要忽略类型时才使用极少场景3、判断null、布尔、数字、字符串混合比较时严禁弱比较。补充1、!弱不等同样自动转类型2、!严格不等推荐var_dump(123 ! 123); // false var_dump(123 ! 123); // trueMD5对比缺陷进行hash加密出来的字符串如存在0e开头进行弱比较的话会直接判定为true1. 魔术哈希 0e 漏洞演示最经典业务漏洞?php // 已知明文md5后都是 0e 开头魔术哈希 $str1 240610708; //md5 0e462097431906509019562988736854 $str2 QNKCDZO; //md5 0e830400458393077759938481653614 $md5_1 md5($str1); $md5_2 md5($str2); echo str1{$str1} md5 {$md5_1}\n; echo str2{$str2} md5 {$md5_2}\n\n; // 弱比较 两个完全不同的哈希判定相等漏洞出现 if ($md5_1 $md5_2) { echo 【危险 弱比较】两个不同md5判定相等验证绕过\n; } else { echo 判断不相等\n; } // 严格比较 类型一致且字符完全匹配才相等安全 if ($md5_1 $md5_2) { echo 判断相等\n; } else { echo 【安全 强比较】md5字符串内容不同判定不相等\n; } // 再和数字0对比 echo \nmd5_1 0 . var_export($md5_1 0, true) . \n; // true echo md5_1 0 . var_export($md5_1 0, true) . \n;// false ? //md5_1 0true //md5_1 0false2. MD5 碰撞缺陷演示不同明文相同 md5?php // 一对经典MD5碰撞原文 $a1 \x4d\xdc\x8b\x7a\x0c\x03\x4b\xa8\x3e\x48\x9f\xcf\x13\x8a\x31\x57; //md5: 1faa01cd2e51d69cfc49a1ad938a2bc6 $a2 \x4d\xdc\x8b\x7a\x0c\x03\x4b\xa8\x3e\x48\x9f\xcf\x13\x8a\x31\x58; //md5: 8d3e28fb7fa02974830f8fa3afd88d0b $md5_a md5($a1); $md5_b md5($a2); echo 字符串1 md5: {$md5_a}\n; echo 字符串2 md5: {$md5_b}\n; if ($md5_a $md5_b) { echo 缺陷两段完全不同内容MD5值完全一样MD5碰撞\n; } ?还有一些0e开头的字符串QNKCDZO 0e830400451993494058024219903391 240610708 0e462097431906509019562988736854 s878926199a 0e545993274517709034328855841020 s155964671a 0e342768416822451524974117254469 s214587387a 0e848240448830537924465865611904 s214587387a 0e848240448830537924465865611904 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s1885207154a 0e509367213418206700842008763514函数strcmp类型比较缺陷核心漏洞原理1、strcmp(string $str1, string $str2)要求两个参数都必须是字符串2、低版本 PHPPHP5、早期 PHP7中如果传入数组给strcmp函数会报错警告但不会终止程序返回值等于 03、strcmp(a,b) 0代表两个字符串相等攻击者传入数组直接绕过等值判断4、配合表单 / GET/POST 传参传入数组?pass[]1即可触发漏洞。?php // 预设正确密码字符串 $real_pwd admin123; // 场景1正常传字符串strcmp正常判断 $input_str admin123; $res1 strcmp($input_str, $real_pwd); echo 【正常传入正确字符串】strcmp返回{$res1}\n; if ($res1 0) { echo 正常校验密码正确\n\n; } $input_wrong 123456; $res2 strcmp($input_wrong, $real_pwd); echo 【正常传入错误字符串】strcmp返回{$res2}\n; if ($res2 0) { echo 密码正确\n; } else { echo 正常校验密码错误\n\n; } // 场景2漏洞关键点——传入数组触发strcmp缺陷绕过 $input_arr []; // 攻击者传入数组如 ?pwd[]xxx $res3 strcmp($input_arr, $real_pwd); echo 【恶意传入数组】strcmp返回{$res3}\n; if ($res3 0) { echo 漏洞触发数组绕过密码校验直接登录成功\n; } else { echo 密码错误\n; } ?运行结果和效果【正常传入正确字符串】strcmp返回0 正常校验密码正确 【正常传入错误字符串】strcmp返回-1 正常校验密码错误 Warning: strcmp() expects parameter 1 to be string, array given in ... 【恶意传入数组】strcmp返回0 漏洞触发数组绕过密码校验直接登录成功实战实例?php // 模拟后端逻辑从GET获取pwd参数 $admin_pass secret666; $user_input $_GET[pwd]; // 攻击者访问shturl.cc/Fr[]1 if (strcmp($user_input, $admin_pass) 0) { echo 欢迎管理员登录成功被数组绕过; } else { echo 密码错误; }攻击payload?pwd[]abc //$_GET[pwd] 得到数组strcmp 报错返回 0判断成立直接登录。修复建议1、对比前强制校验参数类型必须是字符串if (!is_string($user_input)) { die(参数非法); } if (strcmp($user_input, $admin_pass) 0) { // 校验通过 }2、升级 PHP 到高版本PHP7.4 传入数组会直接抛出致命错误终止程序3、密码校验使用password_hashpassword_verify放弃明文字符串比对4、使用严格全等替代字符串比较函数。补充同类字符串比较函数同样有坑1、strcasecmp、strncmp、strnatcmp低版本 PHP 传入数组都会返回 02、switch弱类型匹配、MD5 0e 魔术哈希、strcmp数组绕过是 PHP 审计三大经典弱类型漏洞。函数Bool类型比较缺陷漏洞核心原理json_decode()、unserialize()解析数据时会原生生成true/false布尔值业务中常使用弱比较、strcmp、等值判断校验数据布尔值和数字 / 字符串弱比较会发生隐式类型转换逻辑被绕过两种典型场景JSON 里true/false解析为 PHP bool反序列化字符串b:1;/b:0;得到true/false弱比较规则true 任意非0数字 / 非空字符串→ truefalse 0 / / [] / null→ truejson_decode 布尔解析缺陷演示?php // 业务需求要求传入数字 123 才能通过校验 $target 123; // 正常传入数字JSON $json_num 123; $data1 json_decode($json_num); var_dump($data1); // int(123) echo 数字123 123 . var_export($data1 $target, true) . \n\n; // 恶意传入布尔true JSON $json_true true; $data2 json_decode($json_true); var_dump($data2); // bool(true) // 弱比较缺陷true 和任何非零数字都相等 echo 布尔true 123 . var_export($data2 $target, true) . \n; if ($data2 $target) { echo 漏洞触发传入true直接绕过数字校验\n\n; } // 严格比较才安全 echo 布尔true 123 . var_export($data2 $target, true) . \n\n; // 场景2false绕过0、空字符串校验 $target_zero 0; $json_false false; $data3 json_decode($json_false); var_dump($data3); // bool(false) echo false 0 . var_export($data3 $target_zero, true) . \n; if ($data3 $target_zero) { echo 漏洞false绕过0值校验\n; } ?输出结果int(123) 数字123 123true bool(true) 布尔true 123true 漏洞触发传入true直接绕过数字校验 布尔true 123false bool(false) false 0true 漏洞false绕过0值校验实战业务漏洞示例接口参数校验?php // 接口接收json参数 $json_input $_POST[data]; $allow_id 999; // 仅允许id999操作 $obj json_decode($json_input); // 危险弱比较 if ($obj-id $allow_id) { echo 权限通过执行敏感操作; }攻击payload攻击传入 JSON{id:true} true 999 成立直接越权。unserialize 布尔反序列化缺陷演示?php $secret admin666; // 1. 反序列化得到布尔true $ser_true b:1;; $val1 unserialize($ser_true); var_dump($val1); // bool(true) // 弱比较true 任意非空字符串 echo true admin666 . var_export($val1 $secret, true) . \n; if ($val1 $secret) { echo 漏洞布尔true绕过密码校验\n\n; } // 2. 布尔false对比空、0 $ser_false b:0;; $val2 unserialize($ser_false); var_dump($val2); // bool(false) echo false . var_export($val2 , true) . \n; echo false 0 . var_export($val2 0, true) . \n; // 3. 搭配strcmp双重漏洞 echo \nstrcmp(bool(true), admin666) 返回 . strcmp($val1, $secret) . \n; if (strcmp($val1, $secret) 0) { echo 双重缺陷布尔传入strcmp返回0校验绕过; } ? //序列化布尔格式 //b:1; → true //b:0; → false输出结果bool(true) true admin666true 漏洞布尔true绕过密码校验 bool(false) false true false 0true Warning: strcmp() expects parameter 1 to be string, bool given strcmp(bool(true), admin666) 返回0 双重缺陷布尔传入strcmp返回0校验绕过修复建议全部使用严格全等判断杜绝隐式类型转换解析后先校验数据类型预期数字就用is_int()预期字符串用is_string()$obj json_decode($json_input); if (!is_int($obj-id) || $obj-id ! 999) { die(参数非法); }禁止将未经类型校验的数据直接传入strcmp/strcasecmp等字符串函数尽量避免使用unserialize反序列化用户可控输入改用 json 存储数据密码、敏感标识对比统一使用password_verify或完整字符串严格匹配。函数switch类型比较缺陷漏洞核心原理1、switch的匹配规则会把 switch 括号里的变量强制转为 int再和每个 case 值对比弱类型匹配不是严格全等2、如果传入字符串、布尔、数组会自动转整型极易出现预期外匹配3、典型场景case 写数字传入带数字开头的字符串、字符串数字、true/false都会错误命中 case。漏洞示例示例 1数字 case 匹配数字开头字符串最常见绕过?php $id 2abc; // 可控输入用户传入字符串 switch ($id) { case 1: echo 匹配到 1; break; case 2: echo 漏洞触发字符串2abc命中 case 2; break; case 3: echo 匹配到 3; break; default: echo 无匹配; }输出结果漏洞触发字符串2abc命中 case 2 原因2abc 转 int 等于 2和 case 2 匹配成功。示例 2true 匹配所有非 0 数字 case?php $val true; switch ($val) { case 0: echo 匹配0; break; case 999: echo 漏洞true 命中任意非0数字case999; break; default: echo 默认分支; }输出结果漏洞true 命中任意非0数字case999 原理true 强转 int 1所有非 0 数字 case 都会和 true 弱相等。示例 3false 匹配 case 0?php $val false; switch ($val) { case 0: echo 漏洞false 转int0命中case 0; break; case 100: echo 匹配100; break; }示例 4实战漏洞场景后台权限判断?php // 业务逻辑只有id888才允许管理员操作 $user_input $_GET[uid]; // 攻击者传入 ?uid888admin switch ($user_input) { case 888: echo 管理员权限删除全部数据; break; default: echo 普通用户无权限; }攻击payload?uid888test 直接命中 case 888越权操作。示例 5纯数字字符串也会误匹配?php $num_str 666; switch ($num_str) { case 666: echo 字符串666匹配数字666弱类型转换生效; }修复建议方案 1case 统一写字符串强类型匹配?php $id 2abc; switch ((string)$id) { // 统一转字符串 case 1: echo 匹配1; break; case 2: echo 不会命中; break; }方案 2放弃 switch用 if 严格全等推荐?php $user_input $_GET[uid]; $target 888; // 先校验类型再严格对比 if (is_int($user_input) $user_input $target) { echo 管理员权限; } else { echo 无权限; }函数in_array数组比较缺陷in_array($needle, $haystack, $strict)不传第 3 个参数$stricttrue使用弱比较自动类型转换存在绕过漏洞传入$stricttrue使用严格比较值 类型完全一致才匹配array_search规则完全一样第三个参数控制是否严格匹配。漏洞示例示例 1数字数组匹配数字开头字符串最常见漏洞?php $arr [1, 2, 3, 999]; $input 2abc; // 不开启严格匹配弱比较 var_dump(in_array($input, $arr)); // bool(true) 误匹配 var_dump(array_search($input, $arr)); // int(1) 返回下标判定存在 echo PHP_EOL; // 开启严格匹配 true安全 var_dump(in_array($input, $arr, true)); // bool(false) var_dump(array_search($input, $arr, true)); // bool(false)原因字符串 2abc 转数字为 2数组里存在 2弱比较判定相等。示例 2true 匹配所有非 0 数字?php $allow [10, 20, 30]; $val true; var_dump(in_array($val, $allow)); // true var_dump(in_array($val, $allow, true));// false示例 3false 匹配 0、空字符串?php $test [0, , hello]; $input false; var_dump(in_array($input, $test)); // true var_dump(in_array($input, $test, true));// false示例 40 匹配任意纯字母字符串?php $list [0, 5, 6]; $str admin; var_dump(in_array($str, $list)); // true var_dump(in_array($str, $list, true));// false实战演示?php // 白名单仅允许指定数字ID访问后台 $white_uid [1001, 2002, 3003]; $user_id $_GET[uid]; // 攻击者传入 ?uid1001test // 危险代码未加第三个参数弱比较 if (in_array($user_id, $white_uid)) { echo 白名单校验通过进入后台漏洞绕过; } echo PHP_EOL; // 安全代码开启严格匹配 if (in_array($user_id, $white_uid, true)) { echo 白名单校验通过; } else { echo 非法用户拒绝访问; }示例 5array_search 漏洞利用获取下标做判断?php $ids [888, 999, 666]; $input 888xxx; $res array_search($input, $ids); if ($res ! false) { echo 找到匹配越权操作; }修复建议1、只要业务需要精确匹配in_array/array_search必须带上第三个参数truein_array($needle, $arr, true); array_search($needle, $arr, true);2、提前校验参数类型预期数字用is_int()预期字符串用is_string()3、涉及权限、ID、验证码、白名单校验禁止使用松散比较。数组比较缺陷漏洞核心原理1、md5(数组)/sha1(数组)传入数组参数会触发警告函数返回NULL2、两个变量都经过md5()处理且都传入数组结果都等于NULL3、NULL NULL严格比较结果为true直接绕过全等判断4、哪怕用的是最严格的依然能绕过校验。漏洞示例基础演示md5 传数组返回 NULL?php // 1. 正常字符串md5 $str 123456; $md5_str md5($str); var_dump($md5_str); // string(32) e10adc3949ba59abbe56e057f20f883e echo PHP_EOL; // 2. 传入数组md5报错返回NULL $arr [1,2,3]; $md5_arr md5($arr); var_dump($md5_arr); // NULL ? //输出会附带警告Warning: md5() expects parameter 1 to be string, array given但函数返回值固定是 NULL。核心漏洞双数组 md5 后 NULL NULL 绕过?php // 业务逻辑要求两个输入md5完全一致使用严格 $input1 $_GET[a]; $input2 $_GET[b]; $md5_1 md5($input1); $md5_2 md5($input2); echo md5(\$input1) ; var_dump($md5_1); echo md5(\$input2) ; var_dump($md5_2); // 这里用的是严格全等 看似安全 if ($md5_1 $md5_2) { echo \n【漏洞触发】md5结果严格相等校验通过; } else { echo \n校验失败; }攻击payload?a[]testb[]abc修复建议1、提前校验参数必须为字符串过滤数组$a $_GET[a]; $b $_GET[b]; if (!is_string($a) || !is_string($b)) { die(参数必须为字符串禁止数组); } $md5_1 md5($a); $md5_2 md5($b); if ($md5_1 $md5_2) { // 业务逻辑 }2、关闭前端数组传参接口统一接收字符串3、密码校验改用password_hash()/password_verify避免单纯哈希对比4、自定义判断捕获NULL情况直接拦截。补充sha1()同理sha1()接收数组同样返回NULL同样可以用数组绕过判断?php $x $_GET[x]; $y $_GET[y]; if (sha1($x) sha1($y)) { echo sha1严格比较被数组绕过; } //攻击payload?x[]ay[]b

相关新闻