FlawOpen

flawopen.com/类型转换/PHP ==

PHP == 适合比较哈希吗?

CWE-697: Incorrect Comparison参考页
简要回答

不适合。使用 == 时,PHP 会按数值比较两条数字字符串,因此科学计数法形式的不同字符串在 PHP 8 中仍可能相等。先验证输入类型。精确值使用 ===;秘密令牌或 MAC 使用 hash_equals(),避免依赖内容的时间泄露。

存在漏洞
<?php
// Both strings are numeric and represent zero
$storedHash = "0e462097431906509019562988736854";
$submittedHash = "0e987654321";

// == returns true on PHP 7 and PHP 8
if ($storedHash == $submittedHash) {
    login();
}

// Request data may also have the wrong type:
$submitted = $_POST['token'] ?? null;
if ($storedHash == $submitted) {
    login();
}
// ?token[]=x supplies an array, not a string.
已修复
<?php
$submitted = $_POST['token'] ?? null;
if (!is_string($submitted)) {
    http_response_code(400);
    exit;
}

// For secret tokens or MACs:
$tokenIsValid = hash_equals($storedToken, $submitted);
if ($tokenIsValid) {
    login();
}

// For non-secret identifiers, strict
// comparison checks both type and value:
$idsMatch = ($expectedId === $submitted);

// Passwords require password_verify(),
// not a hand-written hash comparison:
$passwordIsValid = password_verify($plain, $storedPasswordHash);

“Magic Hash”问题

PHP 8 前后都会按数值比较两条数字字符串。以 0e 开头且后续只有数字的十六进制哈希,是数值为零的有效科学计数法字符串。例如 0e462097431906509019562988736854 与 0e987654321 虽然不同,但都表示零,因此用 == 比较相等。PHP 8 改变了字符串与数字的比较规则,没有改变两条数字字符串之间的这种比较。

PHP 8 改变了数字与非数字字符串的比较:现在比较它们的字符串形式,而不会把非数字字符串当作零。它没有消除两条数字 0e… 字符串之间的经典问题。无论 PHP 版本如何,都不要用 == 做身份验证。

请求类型也是信任边界的一部分

请求参数不一定是字符串。像 ?token[]=x 这样的查询会生成数组。将其传入面向字符串的 API,可能根据 PHP 版本和函数触发类型错误或非预期路径。在比较或调用 hash_equals() 前,先在请求边界确认它是字符串。

可靠防护有两步:在边界验证输入类型,再选择符合安全需求的比较方式。=== 检查类型和值;hash_equals() 比较秘密字符串时避免内容相关的时间泄露。

常见问题

PHP 8 完全修复了吗?

没有。PHP 8 改变了字符串与数字的比较,但两条数字字符串仍按数值比较。不同的 0e… 字符串仍可能相等。精确类型值用 ===,秘密值用 hash_equals(),并在所有支持版本中验证输入类型。

为什么用 hash_equals() 而不是 ===?

PHP 将 hash_equals() 说明为可防止字符串内容通过执行时间泄露;普通比较耗时可能取决于差异出现的位置。先传入已知秘密,再传入用户字符串。密码应使用 password_verify()。

其他语言也有这种问题吗?

JavaScript 的 == 也会带来比较陷阱,因此通常建议默认使用 ===。PHP 中这种特定 Magic Hash 现象来自十六进制哈希与科学计数法的比较。

参考资料

另请参阅 Rust 的 unsafe 块仍能保证内存安全吗? strncpy() 比 strcpy() 更安全吗? CWE 与 CVE:有什么区别? Mass Assignment:风险与安全字段白名单 Serverless 状态泄漏:按请求隔离用户数据