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

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Typverwirrung bei Authentifizierungsprüfungen kann unautorisierten Zugriff ermöglichen.
IntegritätBereich: Integrität

Anwendungsdaten ändern - Falsche Vergleiche können dazu führen, dass falsche Daten verarbeitet oder geändert werden.
VertraulichkeitBereich: 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


CVE-Beispiele

  • CVE-2015-8562 - Type Juggling in PHP führte zu Authentifizierungsumgehung.

  • CVE-2014-0166 - Integer-Vergleichsprobleme in WordPress.


Referenzen

  1. MITRE Corporation. "CWE-1024: Comparison of Incompatible Types." https://cwe.mitre.org/data/definitions/1024.html

  2. OWASP. "PHP Type Juggling Vulnerabilities." https://owasp.org/www-pdf-archive/PHPMagicTricks-TypeJuggling.pdf

  3. PHP Manual. "Comparison Operators." https://www.php.net/manual/en/language.operators.comparison.php