flawopen.com/Type Juggling/PHP ==
Безопасно ли сравнивать хэши через PHP ==?
Нет. При == 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 изменил сравнение строки с числом, но две числовые строки всё ещё сравниваются численно. Разные строки 0e… могут быть равны. Используйте === для точных типизированных значений или hash_equals() для секретов и проверяйте типы во всех поддерживаемых версиях.
PHP указывает, что hash_equals() защищает содержимое строки от временных атак; обычное сравнение может занимать разное время в зависимости от позиции различия. Передавайте известный секрет первым аргументом, пользовательскую строку — вторым. Для паролей используйте password_verify().
В JavaScript тоже есть неожиданные результаты оператора ==, поэтому обычно рекомендуют ===. Специфика magic hash в PHP связана с числовым сравнением шестнадцатеричных строк в научной нотации.