Оператор == у PHP перед порівнянням приводить типи. Правила складні, і на них будувалися обходи автентифікації й перевірок.
«Магічні» хеші. Два числові рядки порівнюються як числа. Рядок виду "0e" + цифри - число в експоненційному записі, тобто нуль:
"0e123456" == "0e987654"; // true: обидва дорівнюють 0
md5('240610708') == md5('QNKCDZO'); // true - обидва хеші починаються з 0e і далі лише цифри
Перевірка if ($providedHash == $storedHash) пропускає інший рядок з таким самим «нульовим» хешем. Так обходили перевірку токенів скидання пароля й підписів.
Інші пастки (поведінка PHP 8):
"1" == "01"; // true - числові рядки
"10" == "1e1"; // true
100 == "1e2"; // true
null == false; // true
[] == false; // true
"0" == false; // true
PHP 8 виправив найгірше: "abc" == 0 тепер false (у PHP 7 було true), а in_array('abc', [0]) - false. Але порівняння числових рядків і null/false лишилися.
Де це небезпечно:
- перевірка токенів, хешів, підписів, кодів підтвердження;
in_array/array_searchбез третього аргументуtrue- нестроге порівняння;switch- теж використовує==;- JSON-ввід: клієнт надсилає
{"code": true}чи число замість рядка, і$code == $expectedповодиться несподівано. Типи з JSON не гарантовані - їх треба валідувати.
Захист:
===і!==за замовчуванням - порівняння без приведення типів;hash_equals($known, $user)для секретів - строге порівняння ще й у постійному часі (захист від атак за часом);in_array($value, $list, true),array_search(..., true);matchзамістьswitch- використовує строге порівняння;declare(strict_types=1)- строгі типи аргументів функцій (не впливає на==, але ловить передачу не того типу);- валідація типів вхідних даних (
'code' => ['required', 'string', 'size:6']); - статичні аналізатори (PHPStan, Psalm) і правила code style попереджають про
==.