flawopen.com/타입 저글링/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로 시작하고 뒤에 숫자만 오는 16진 해시는 값이 0인 유효한 과학적 표기 문자열입니다. 예를 들어 0e462097431906509019562988736854와 0e987654321은 서로 다른 문자열이지만 모두 0을 뜻하므로 ==에서 같습니다. PHP 8은 문자열과 숫자의 비교를 바꿨지만 두 숫자 문자열 간 이 동작은 바꾸지 않았습니다.
PHP 8은 숫자와 비숫자 문자열의 비교를 바꿨습니다. 이제 비숫자 문자열을 0으로 취급하지 않고 문자열 형태를 비교합니다. 하지만 두 숫자 0e… 문자열 간 비교는 그대로입니다. PHP 버전과 관계없이 인증에 ==를 사용하지 마세요.
요청 유형도 신뢰 경계의 일부입니다
요청 매개변수가 항상 문자열인 것은 아닙니다. ?token[]=x 같은 쿼리는 배열을 만듭니다. 문자열 API에 전달하면 PHP 버전과 함수에 따라 타입 오류나 예상 밖의 경로가 발생할 수 있습니다. 비교하거나 hash_equals()를 호출하기 전에 요청 경계에서 문자열인지 확인하세요.
지속 가능한 방어는 두 단계입니다. 경계에서 입력 유형을 검증하고 요구사항에 맞는 비교를 선택하세요. ===는 유형과 값을 확인하고, hash_equals()는 내용에 따른 시간 누출 없이 비밀 문자열을 비교합니다.
자주 묻는 질문
아니요. PHP 8은 문자열-숫자 비교를 바꿨지만 두 숫자 문자열은 여전히 숫자로 비교됩니다. 서로 다른 0e… 문자열이 같을 수 있습니다. 정확한 유형 값에는 ===, 비밀값에는 hash_equals()를 사용하고 지원하는 모든 버전에서 입력 유형을 검증하세요.
PHP는 hash_equals()가 문자열 내용에 대한 타이밍 공격을 막는다고 설명합니다. 일반 비교는 차이가 발생하는 위치에 따라 시간이 달라질 수 있습니다. 알려진 비밀값을 첫 번째 인자로, 사용자 문자열을 두 번째로 전달하세요. 비밀번호에는 password_verify()를 사용하세요.
JavaScript의 ==도 혼란스러운 결과가 있어 보통 === 사용이 권장됩니다. PHP의 Magic Hash 현상은 16진 문자열을 과학적 표기 숫자로 비교하는 데서 비롯됩니다.