flawopen.com/Type Juggling/PHP ==
Ist PHP == für Hashvergleiche sicher?
Nein. Bei == vergleicht PHP zwei numerische Zeichenketten numerisch; unterschiedliche Zeichenketten in wissenschaftlicher Schreibweise können daher auch unter PHP 8 gleich sein. Prüfe den Eingabetyp. Verwende === für exakte Werte und hash_equals() für geheime Token oder MACs, um inhaltsabhängige Zeitinformationen zu vermeiden.
<?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);
Das Problem mit „Magic Hashes“
PHP vergleicht zwei numerische Zeichenketten vor und nach PHP 8 numerisch. Ein hexadezimaler Hash, der mit 0e beginnt und nur Ziffern enthält, ist eine gültige Zeichenkette in wissenschaftlicher Schreibweise mit dem Zahlenwert null. Daher sind etwa 0e462097431906509019562988736854 und 0e987654321 verschieden, entsprechen aber beide null und sind mit == gleich. PHP 8 änderte Zeichenkette-Zahl-Vergleiche, nicht diesen Fall zweier numerischer Zeichenketten.
PHP 8 änderte Vergleiche zwischen einer Zahl und einer nichtnumerischen Zeichenkette: Nun werden ihre Zeichenkettenformen verglichen, statt die Zeichenkette als null zu behandeln. Der klassische Vergleich zweier numerischer 0e…-Zeichenketten blieb bestehen. Verwende == unabhängig von der Version nicht für Authentifizierung.
Request-Typen gehören zur Vertrauensgrenze
Request-Parameter sind nicht zwingend Zeichenketten. Eine Abfrage wie ?token[]=x liefert ein Array. Die Übergabe an eine stringbasierte API kann je nach PHP-Version und Funktion einen Typfehler oder einen unerwarteten Pfad auslösen. Verlange am Request-Eingang eine Zeichenkette, bevor du vergleichst oder hash_equals() aufrufst.
Dauerhafter Schutz besteht aus zwei Schritten: Eingabetyp an der Grenze prüfen und anschließend passend vergleichen. === prüft Typ und Wert; hash_equals() vergleicht Geheimnisse ohne inhaltsabhängige Zeit-Lecks.
Häufige Fragen
Nein. PHP 8 änderte Zeichenkette-Zahl-Vergleiche, aber zwei numerische Zeichenketten werden weiterhin numerisch verglichen. Verschiedene 0e…-Zeichenketten können gleich sein. Verwende === für exakt typisierte Werte oder hash_equals() für Geheimnisse und prüfe Typen in jeder unterstützten Version.
PHP dokumentiert hash_equals() als sicher gegen zeitbasierte Angriffe auf den Zeichenketteninhalt; ein normaler Vergleich kann abhängig von der Position des Unterschieds unterschiedlich lange dauern. Übergib zuerst das bekannte Geheimnis, dann die Benutzereingabe. Für Passwörter gilt password_verify().
Auch JavaScript hat überraschende ==-Vergleiche; deshalb wird === dort meist standardmäßig empfohlen. Die spezielle Magic-Hash-Form entsteht in PHP durch den Vergleich hexadezimaler Zeichenketten in wissenschaftlicher Schreibweise.