Vergleich inkompatibler Typen
Beschreibung
Vergleich inkompatibler Typen tritt auf, wenn ein Produkt einen Vergleich zwischen zwei Entitäten unterschiedlicher Typen durchführt, wobei der Vergleich aufgrund von Typzwang, impliziter Konvertierung oder grundlegender Inkompatibilität zwischen den Typen unerwartete oder falsche Ergebnisse liefern kann. Dies ist besonders verbreitet in dynamisch typisierten Sprachen, in denen implizite Typkonvertierungen automatisch erfolgen. Der Vergleich kann syntaktisch erfolgreich sein, aber das beabsichtigte Sicherheits- oder logische Ergebnis nicht liefern, was zu Authentifizierungsumgehungen, Autorisierungsfehlern oder Logikfehlern führt.
Risiko
Diese Schwachstelle kann dazu führen, dass Sicherheitskontrollen stillschweigend versagen. In PHP kann der Vergleich eines Strings mit einer Ganzzahl mit "==" unerwartete True-Ergebnisse liefern (z.B. "0e123" == 0 ergibt true). JavaScripts lose Gleichheit kann Strings mit Zahlen oder null mit undefined abgleichen. Angreifer können Eingaben erstellen, die Typzwang ausnutzen, um Authentifizierungsprüfungen, SQL-Injection-Filter oder Zugriffskontrollen zu umgehen. Das Risiko ist hoch, weil der Code korrekt erscheint und Tests mit erwarteten Eingabetypen bestehen kann, aber mit sorgfältig konstruierten bösartigen Eingaben versagt.
Lösung
Verwenden Sie strikte Typvergleichsoperatoren, wo verfügbar (=== in PHP/JavaScript, Object.equals() in Java). Validieren und bereinigen Sie Eingabetypen vor dem Vergleich. Konvertieren Sie Werte explizit in erwartete Typen vor dem Vergleich. Verwenden Sie statische Typprüfung oder strikten Modus in Sprachen, die dies unterstützen. Vermeiden Sie den direkten Vergleich von Benutzereingaben mit gespeicherten Werten ohne Typnormalisierung. Fügen Sie in dynamisch typisierten Sprachen explizite Typprüfungen vor sicherheitskritischen Vergleichen hinzu. Erwägen Sie die Verwendung stark typisierter Sprachen für sicherheitskritische Komponenten.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Schutzmechanismus umgehen - Typverwirrung bei Authentifizierungsprüfungen kann unautorisierten Zugriff ermöglichen. |
| Integrität | Bereich: Integrität Anwendungsdaten ändern - Falsche Vergleiche können dazu führen, dass falsche Daten verarbeitet oder geändert werden. |
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Autorisierungsumgehungen durch Typverwirrung können sensible Daten offenlegen. |
Beispielcode und Lösung
Verwundbarer Code
<?php
// Verwundbar: Loser Vergleich mit Typzwang
function verwundbarerLogin($username, $password) {
$stored_hash = getUserHash($username);
// Verwundbar: Verwendet == statt ===
// Wenn $password "0" ist und Hash mit "0e" beginnt (wissenschaftliche Notation)
// behandelt PHP beide als Zahlen: 0 == 0 -> true!
if ($password == $stored_hash) {
return true; // Authentifizierungsumgehung!
}
return false;
}
// Beispiel: Hash "0e462097431906509019562988736854" == "0" ist TRUE
// Weil PHP "0e..." zu 0 konvertiert (wissenschaftliche Notation)
<?php
// Verwundbar: Array vs String-Vergleich
function verwundbareAdminPrüfung($role) {
// Verwundbar: Vergleicht potenziell unterschiedliche Typen
if ($role == 'admin') {
grantAdminAccess();
}
}
// Angriff: Rolle als Array übergeben: role[]=admin
// In PHP: ['admin'] == 'admin' hat unerwartetes Verhalten
// Vor PHP 8: Array verglichen mit String war immer "größer"
// Verwundbar: JavaScript lose Gleichheit
function verwundbareAuthentifizierung(token, storedToken) {
// Verwundbar: Verwendet == statt ===
if (token == storedToken) {
return true;
}
return false;
}
// Ausnutzungen:
// null == undefined -> true
// "0" == 0 -> true
// "" == 0 -> true
// "1" == true -> true
// [] == false -> true (wenn [] konvertiert wird)
// Verwundbar: Vergleich unterschiedlicher Typen aus JSON
function verwundbareZugriffsprüfung(userId, resourceOwnerId) {
// JSON kann Strings haben, URL-Parameter können Strings sein
// Aber Datenbank-IDs könnten Zahlen sein
// Verwundbar: Typen können unterschiedlich sein
if (userId == resourceOwnerId) {
return true; // "1" == 1 ist true, aber was ist mit "1 " == 1?
}
return false;
}
# Verwundbar: Bytes und Strings in Python 3 vergleichen
def verwundbare_signatur_verifizierung(provided, expected):
# Verwundbar: Einer könnte Bytes sein, der andere String
# In Python 3: b"abc" == "abc" ist False (kein Typzwang)
# Aber dieses stille Scheitern wird möglicherweise nicht erkannt
return provided == expected
# Wenn provided Bytes und expected String ist, immer False
# Kann Denial of Service oder unerwartete Ablehnungen verursachen
// Verwundbar: Integer vs Long-Vergleich in Java
public class VerwundbarerVergleich {
public boolean verwundbareIdPrüfung(Object providedId, Long storedId) {
// Verwundbar: Kann Integer mit Long vergleichen
// Integer und Long sind unterschiedliche Typen
// Verwendung von == vergleicht Objektreferenzen, nicht Werte!
if (providedId == storedId) {
return true;
}
// Dies ist auch verwundbar, wenn providedId Integer ist
return providedId.equals(storedId);
// Integer.equals(Long) gibt immer false zurück!
}
}
Sichere Lösung
<?php
// Sicher: Strikter Vergleich mit Typvalidierung
function sichererLogin($username, $password) {
// Sicher: Eingabetypen validieren
if (!is_string($username) || !is_string($password)) {
return false;
}
$stored_hash = getUserHash($username);
if (!is_string($stored_hash)) {
return false;
}
// Sicher: password_verify für ordnungsgemäße Passwortprüfung verwenden
// Behandelt Timing-Angriffe und Typprobleme
return password_verify($password, $stored_hash);
}
// Für andere Vergleiche === (strikte Gleichheit) verwenden
function sichereTokenVergleich($provided, $stored) {
// Sicher: Zuerst Typ prüfen
if (!is_string($provided) || !is_string($stored)) {
return false;
}
// Sicher: Strikten Vergleich verwenden
// Und konstant-zeitlichen Vergleich für Sicherheit
return hash_equals($stored, $provided);
}
<?php
// Sicher: Typsichere Rollenprüfung
function sichereAdminPrüfung($role) {
// Sicher: Zuerst Typ validieren
if (!is_string($role)) {
error_log("Ungültiger Rollentyp: " . gettype($role));
return false;
}
// Sicher: Strikten Vergleich verwenden
if ($role === 'admin') {
grantAdminAccess();
}
}
// Besser: Enums oder Konstanten verwenden
class UserRole {
const ADMIN = 'admin';
const USER = 'user';
const GUEST = 'guest';
public static function isValid($role) {
return in_array($role, [self::ADMIN, self::USER, self::GUEST], true);
}
}
function sichereAdminPrüfungMitEnum($role) {
if (!is_string($role) || !UserRole::isValid($role)) {
return false;
}
return $role === UserRole::ADMIN;
}
// Sicher: JavaScript strikte Gleichheit
function sichereAuthentifizierung(token, storedToken) {
// Sicher: Typvalidierung
if (typeof token !== 'string' || typeof storedToken !== 'string') {
return false;
}
// Sicher: Strikte Gleichheit === verwenden
if (token === storedToken) {
return true;
}
return false;
}
// Sicher: Mit konstant-zeitlichem Vergleich für Sicherheit
const crypto = require('crypto');
function sichereAuthentifizierungSicher(token, storedToken) {
// Sicher: Typvalidierung
if (typeof token !== 'string' || typeof storedToken !== 'string') {
return false;
}
// Sicher: Zuerst Längenprüfung
if (token.length !== storedToken.length) {
return false;
}
// Sicher: Konstant-zeitlicher Vergleich
return crypto.timingSafeEqual(
Buffer.from(token),
Buffer.from(storedToken)
);
}
// Sicher: Typsicherer ID-Vergleich
function sichereZugriffsprüfung(userId, resourceOwnerId) {
// Sicher: Typen explizit normalisieren
const normalisierteUserId = String(userId).trim();
const normalisierteOwnerId = String(resourceOwnerId).trim();
// Sicher: Strikter Vergleich nach Normalisierung
return normalisierteUserId === normalisierteOwnerId;
}
// Alternative: In Zahlen konvertieren, wenn IDs numerisch sind
function sichereZugriffsprüfungNumerisch(userId, resourceOwnerId) {
const numUserId = parseInt(userId, 10);
const numOwnerId = parseInt(resourceOwnerId, 10);
// Sicher: Validieren, dass Konvertierung erfolgreich war
if (isNaN(numUserId) || isNaN(numOwnerId)) {
return false;
}
return numUserId === numOwnerId;
}
# Sicher: Explizite Typbehandlung in Python
def sichere_signatur_verifizierung(provided, expected):
# Sicher: Typen normalisieren
if isinstance(provided, str):
provided = provided.encode('utf-8')
if isinstance(expected, str):
expected = expected.encode('utf-8')
# Sicher: Validieren, dass beide jetzt Bytes sind
if not isinstance(provided, bytes) or not isinstance(expected, bytes):
raise TypeError("Beide Werte müssen Strings oder Bytes sein")
# Sicher: Konstant-zeitlichen Vergleich verwenden
import hmac
return hmac.compare_digest(provided, expected)
# Mit Type Hints für Klarheit
from typing import Union
import hmac
def sichere_signatur_verifizierung_typisiert(
provided: Union[str, bytes],
expected: Union[str, bytes]
) -> bool:
"""Signatur mit ordnungsgemäßer Typbehandlung verifizieren."""
# Zu Bytes normalisieren
if isinstance(provided, str):
provided = provided.encode('utf-8')
if isinstance(expected, str):
expected = expected.encode('utf-8')
return hmac.compare_digest(provided, expected)
// Sicher: Typsicherer Vergleich in Java
public class SichererVergleich {
public boolean sichereIdPrüfung(Object providedId, Long storedId) {
if (providedId == null || storedId == null) {
return false;
}
// Sicher: Vor Vergleich in gleichen Typ konvertieren
Long providedLong;
if (providedId instanceof Long) {
providedLong = (Long) providedId;
} else if (providedId instanceof Integer) {
providedLong = ((Integer) providedId).longValue();
} else if (providedId instanceof String) {
try {
providedLong = Long.parseLong((String) providedId);
} catch (NumberFormatException e) {
return false;
}
} else {
return false; // Nicht unterstützter Typ
}
// Sicher: Jetzt gleiche Typen vergleichen
return providedLong.equals(storedId);
}
// Besser: Generics verwenden und gleichen Typ erfordern
public <T extends Comparable<T>> boolean sichererVergleich(T a, T b) {
if (a == null || b == null) {
return a == b; // Beide null = gleich
}
return a.compareTo(b) == 0;
}
}
Ausgenutzt in der Praxis
PHP Type Juggling in CMS-Systemen (2015)
Mehrere Content-Management-Systeme waren für Type-Juggling-Angriffe anfällig, bei denen Angreifer die Authentifizierung umgehen könnten, indem sie speziell gestaltete Werte verwendeten, die PHPs lose Vergleiche ausnutzten.
WordPress Type-Juggling-Schwachstelle (2014)
Eine Schwachstelle in WordPress ermöglichte es Angreifern, durch Ausnutzung von PHPs losem Typvergleich bei der Authentifizierung Passwort-Resets durchzuführen.
Joomla Type-Juggling-Bypass (2016)
Eine kritische Schwachstelle in Joomla ermöglichte es Angreifern, Authentifizierung durch Type-Juggling-Angriffe zu umgehen.
Tools zum Testen und Ausnutzen
-
Burp Suite - Type-Juggling-Tests.
-
PHP Magic Tricks - Type-Juggling-Payloads.
CVE-Beispiele
-
CVE-2015-8562 - Type Juggling in PHP führte zu Authentifizierungsumgehung.
-
CVE-2014-0166 - Integer-Vergleichsprobleme in WordPress.
Referenzen
-
MITRE Corporation. "CWE-1024: Comparison of Incompatible Types." https://cwe.mitre.org/data/definitions/1024.html
-
OWASP. "PHP Type Juggling Vulnerabilities." https://owasp.org/www-pdf-archive/PHPMagicTricks-TypeJuggling.pdf
-
PHP Manual. "Comparison Operators." https://www.php.net/manual/en/language.operators.comparison.php