flawopen.com/Type Juggling/PHP ==
O == do PHP é seguro para comparar hashes?
Não. Com ==, o PHP compara strings numéricas como números, então strings diferentes em notação científica podem ser iguais mesmo no PHP 8. Valide o tipo da entrada. Para valores exatos use ===; para tokens secretos ou MACs, use hash_equals() para evitar vazamento temporal dependente do conteúdo.
<?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);
O problema dos “magic hashes”
Duas strings numéricas são comparadas numericamente pelo PHP antes e depois da versão 8. Um hash hexadecimal que começa com 0e e continua apenas com dígitos é uma string válida em notação científica cujo valor numérico é zero. Por exemplo, 0e462097431906509019562988736854 e 0e987654321 são strings diferentes, mas ambas representam zero e são iguais com ==. O PHP 8 alterou comparações entre string e número, não esse caso entre duas strings numéricas.
O PHP 8 mudou comparações entre um número e uma string não numérica: agora compara as formas em string, em vez de tratar a string como zero. Isso não removeu a comparação clássica entre duas strings numéricas 0e…. Não use == para autenticação, em nenhuma versão.
Tipos da requisição fazem parte da fronteira
Parâmetros da requisição não são necessariamente strings. Uma consulta como ?token[]=x produz um array. Passá-lo a uma API de strings pode gerar erro de tipo ou seguir um caminho inesperado, dependendo da versão do PHP e da função. Exija uma string na fronteira da requisição antes de comparar ou chamar hash_equals().
A defesa duradoura tem duas partes: valide o tipo da entrada na fronteira e depois use uma comparação adequada ao requisito. === verifica tipo e valor; hash_equals() compara strings secretas sem vazar tempo dependente do conteúdo.
Perguntas frequentes
Não. O PHP 8 mudou comparações string-número, mas duas strings numéricas ainda são comparadas numericamente. Strings 0e… diferentes ainda podem ser iguais. Use === para valores tipados exatos ou hash_equals() para segredos e valide o tipo da entrada em todas as versões suportadas.
O PHP documenta hash_equals() como segura contra ataques de temporização relacionados ao conteúdo; uma comparação comum pode demorar mais ou menos conforme a posição da diferença. Passe primeiro o segredo conhecido e depois a string enviada pelo usuário. Para senhas use password_verify().
Surpresas com comparação flexível também existem em JavaScript com ==; por isso === costuma ser o padrão recomendado. A forma específica de magic hash decorre da comparação de strings hexadecimais com notação científica no PHP.