flawopen.com/Type Juggling/PHP ==
¿Es seguro PHP == para comparar hashes?
No. Con ==, PHP compara numéricamente dos cadenas numéricas; por eso cadenas distintas en notación científica pueden ser iguales incluso en PHP 8. Valida el tipo de entrada. Usa === para valores exactos y hash_equals() para tokens secretos o MAC, evitando filtraciones temporales según el contenido.
<?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);
El problema de los “magic hashes”
PHP compara numéricamente dos cadenas numéricas tanto antes como después de PHP 8. Un hash hexadecimal que empieza por 0e y continúa solo con dígitos es una cadena válida en notación científica cuyo valor es cero. Por ejemplo, 0e462097431906509019562988736854 y 0e987654321 son cadenas distintas, pero ambas representan cero y son iguales con ==. PHP 8 cambió las comparaciones entre cadena y número, no este caso entre dos cadenas numéricas.
PHP 8 cambió las comparaciones entre un número y una cadena no numérica: ahora compara sus formas de cadena, en lugar de tratar la cadena como cero. No eliminó la comparación clásica entre dos cadenas numéricas 0e…. No uses == para autenticación, sin importar la versión.
Los tipos de la petición forman parte del límite
Los parámetros de petición no siempre son cadenas. Una consulta como ?token[]=x produce un array. Pasarlo a una API de cadenas puede causar un error de tipo o una ruta inesperada, según la versión de PHP y la función. Exige una cadena en el límite de la petición antes de comparar o llamar a hash_equals().
La defensa duradera tiene dos partes: valida el tipo en el límite y luego usa una comparación adecuada al requisito. === comprueba tipo y valor; hash_equals() compara secretos sin filtrar tiempos dependientes del contenido.
Preguntas frecuentes
No. PHP 8 cambió las comparaciones cadena-número, pero dos cadenas numéricas aún se comparan numéricamente. Cadenas 0e… distintas todavía pueden coincidir. Usa === para valores tipados exactos o hash_equals() para secretos y valida tipos en todas las versiones admitidas.
PHP documenta hash_equals() como segura frente a ataques de temporización sobre el contenido; una comparación normal puede variar según dónde difieran las cadenas. Pasa primero el secreto conocido y después la cadena del usuario. Para contraseñas usa password_verify().
JavaScript también tiene sorpresas con ==; por eso === suele ser la opción predeterminada recomendada. El caso específico de magic hash surge al comparar cadenas hexadecimales con notación científica en PHP.