Type Coercion in Authentication Checks
debt(d5/e2/b3/t8)
Closest to 'specialist tool catches' (d5), phpstan and semgrep rules per detection_hints can flag == on token/hash comparisons, but default PHP linters don't catch it — needs a security-aware SAST rule.
Closest to 'one-line patch' (e1) but slightly higher (e2) because quick_fix involves swapping == for ===, hash_equals(), or password_verify() across multiple comparison sites — each fix is one line but you must find them all.
Closest to 'localised tax' (b3), applies to authentication code paths specifically; doesn't shape the whole system but does require ongoing vigilance in every auth-touching component.
Closest to 'serious trap' (t7) leaning catastrophic (t8) — the misconception that strcmp() returns 0 on match (intuitively 'success') colliding with 0 == false is exactly the contradiction-of-similar-concept pattern; the 'obvious' comparison operators silently authenticate attackers via magic hashes.
TL;DR
Explanation
PHP's == operator uses type juggling. Classic attacks: (1) Magic hash collision: two MD5 hashes starting with 0e (like 0e462097431906509019562988736854) are both '0' in scientific notation, making them == equal. (2) 0 == 'anything' because PHP coerces 'anything' to 0. (3) True == 'anything'. The fix: always use === (strict equality) for password, token, and hash comparisons. Use hash_equals() for timing-safe string comparison. PHP's password_verify() and hash_equals() are safe. Never compare hashes with == or strcmp().
Common Misconception
Why It Matters
Common Mistakes
- Using == to compare password hashes — magic hash bypass.
- Using strcmp() for security comparisons — timing attack + type coercion.
- Not using hash_equals() for HMAC verification.
Code Examples
// Vulnerable: 0 == 'admin' is TRUE in PHP
if ($token == $expectedToken) { /* bypassable */ }
// Vulnerable: magic hash
$hash = md5($password); // e.g. '0e123...'
if ($hash == $storedHash) { /* 0e... == 0e... */ }
// Always strict comparison:
if ($token === $expectedToken) { /* correct */ }
// For timing-safe hash comparison:
if (hash_equals($storedHash, $computedHash)) { /* safe */ }
// For passwords, always use password_verify:
if (password_verify($input, $storedHash)) { /* safe */ }