PHP 弱类型陷阱:那些等号比较坑过你的地方

阿乐
阿乐 星耀SVIP管理员 黑卡会员
发布于 2026-09-14 12:07 ·2 浏览 ·0 回复

PHP 的 `==` 会先做类型转换再比较,`===` 不会——所有涉及身份、权限、金额、Token 的比较,一律用 `===` 或 `hash_equals()`,这一条能挡掉九成以上的弱类型事故。

一、坑的根源:`==` 的类型转换规则

结论:`==` 在不同 PHP 版本上的行为并不一致,这才是最危险的地方。

PHP 8.0 之前,非数字字符串和数字比较会先把字符串转成数字,`"abc" == 0` 返回 `true`;PHP 8.0 起改为按字符串比较,返回 `false`。同一行代码,PHP 7.4 和 8.0 结果相反。Clara BBS 的兼容区间是 PHP 7.4-8.5,同一份代码可能跑在完全不同的比较语义上——如果你写插件时习惯用 `==` 判断版本号、状态码、用户输入,升级 PHP 时行为会静默变化,不报错,但逻辑已经错了。

另一个经典是「0e 魔法哈希」:PHP 里 `"0e123"` 会被当成科学计数法(0 乘以 10 的 123 次方,结果都是 0),所以两个都以 `0e` 开头的 MD5 值用 `==` 比较会相等。凡是拿哈希当密码或签名比较的地方,必须用 `hash_equals($known, $user)`,它同时防类型绕过和时序攻击。

还有「五兄弟」问题:`null`、`false`、`0`、`""`、`"0"` 之间用 `==` 互比,结果经常不符合直觉。`null == 0` 为 `true`,`"" == 0` 为 `true`。

二、这些函数内部也在用 `==`

结论:`in_array()`、`array_search()`、`switch`、`strpos()` 都默认走宽松比较,必须主动收紧。

  • `in_array($needle, $haystack)` 默认松散比较,第三个参数传 `true` 才是严格模式。结论:`in_array()` 永远写满三个参数:`in_array($needle, $haystack, true)`。
  • `array_search()` 同理,第三参数 `strict` 要传 `true`。
  • `switch` 用的是 `==` 语义,`switch("abc")` 在 PHP 7 下能命中 `case 0`,PHP 8 下不会。
  • `strpos()` 找不到时返回 `false`,找到开头时返回 `0`,而 `0 == false` 为 `true`。结论:判断用 `if (strpos($s, $needle) === false)`,或直接用 `str_contains()`(PHP 8.0+)。

另外澄清一个高频误解:`declare(strict_types=1)` 只影响函数参数和返回值的类型强制,不影响 `==`、`in_array()` 这类比较操作。开了严格模式不等于比较就安全了。

三、放到论坛系统里,这些坑长什么样

结论:身份、权限、金额三类比较,是最容易被弱类型钻空子的地方。

Clara BBS 在安全基线上把这几件事交给了更稳妥的机制:密码用 bcrypt 存储,校验走 `password_verify()`,不是把哈希拿出来用 `==` 比;CSRF 是全站统一校验;上传走文件类型白名单加图片二次校验。这些设计本身就是绕开弱类型比较的思路。

但二次开发和写插件时,坑得自己躲:

  1. 权限判断。用户组、版块权限这类值常见写法是 `if ($groupId == 1)`,如果 `$groupId` 来自请求参数、是字符串 `"1abc"`,PHP 7 下会命中管理员分支。改用 `===` 并先做 `intval()` 归一,或直接把允许的组放进数组用 `in_array(..., true)` 判断——Clara BBS 的版块级权限矩阵本身就是「用户组 × 操作」的枚举匹配,插件里沿用这套枚举比自造数值判断安全得多。
  2. 金额与余额。悬赏托管、附件定价、转账记账都绕不开数值比较。浮点数用 `==` 几乎必错(`0.1 + 0.2 == 0.3` 是 `false`),涉及金额时统一用整数最小单位,或至少用 `abs($a - $b) < 0.0001` 这种容差写法。
  3. Token 与签名。任何和用户输入的密钥、令牌比较,一律 `hash_equals()`。
  4. 状态码。数据库里的 `tinyint` 状态字段,从 PDO 取出来可能是字符串,别用 `==` 和整数常量比,用 `(int)` 转换后 `===`。

四、动手清单

结论:把下面四件事做完,弱类型事故基本清零。

  1. 全项目搜索 `==`,逐个人工确认,安全相关的一律换成 `===`。
  2. 搜索 `in_array(` 和 `array_search(`,补上 `true`。
  3. 搜索 `strpos(`,确认判断条件写的是 `=== false`。
  4. 在代码规范里写明:不许用 `==` 比较来自请求、数据库、配置的值——这些来源的类型永远不可信。

判断优先级很简单:能 `===` 就 `===`,哈希用 `hash_equals()`,数组查找加 `strict`,金额别用浮点相等。

弱类型不是 PHP 的缺陷,是它的设计取舍;问题在于你没法控制变量从哪来。凡是外部输入参与的比较,都当成「类型未知」处理,用严格比较收口,就不会在升级 PHP 或换环境时被静默改掉逻辑。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-332.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
阿乐

全部回复 0

还没有回复,来抢沙发~