Fehlerhafte Typkonvertierung oder Cast

Beschreibung

Fehlerhafte Typkonvertierung oder Cast ist eine Schwachstelle, bei der Software ein Objekt, eine Ressource oder Struktur nicht korrekt von einem Typ in einen anderen konvertiert. Dies umfasst fehlerhafte Casts zwischen numerischen Typen (signed/unsigned, unterschiedliche Größen), inkorrekte Pointer-Casts, Union-Typ-Verwechslungen und das Verlassen auf implizite Typumwandlungen mit unerwarteten Ergebnissen. Wenn Typkonvertierungen fehlerhaft sind, kann der resultierende Wert abgeschnitten, falsch vorzeichenerweitert oder komplett fehlinterpretiert werden, was zu Sicherheitslücken wie Buffer Overflows, Speicherkorruption oder Logikfehlern führt.

Risiko

Fehlerhafte Typkonvertierungen schaffen schwerwiegende Sicherheitslücken. Die Konvertierung negativer Signed-Werte zu Unsigned-Typen erzeugt große positive Werte, die bei Verwendung als Größen potentiell Buffer Overflows verursachen. Das Abschneiden von 64-Bit-Werten auf 32-Bit kann zu Heap-Korruption führen, wenn der abgeschnittene Wert für Speicheroperationen verwendet wird. Union-Typ-Verwechslungen ermöglichen die Änderung von Speicher durch eine Typ-Interpretation beim Schreiben und eine andere beim Lesen, was Out-of-Bounds-Zugriffe ermöglicht. Lose Typvergleiche in dynamischen Sprachen können Sicherheitsprüfungen durch Ausnutzung von Typumwandlungsregeln umgehen. Das Risiko wird verstärkt, weil diese Probleme möglicherweise nur bei bestimmten Eingabewerten auftreten, die die fehlerhafte Konvertierung auslosen.

Lösung

Verwenden Sie explizite Typkonvertierungen und validieren Sie Werte vor der Konvertierung. Prüfen Sie, ob Werte in den Bereich des Zieltyps passen, bevor Sie casten. Vermeiden Sie das Mischen von Signed- und Unsigned-Typen in Vergleichen und Arithmetik. Verwenden Sie Festbreiten-Integer-Typen (int32_t, uint64_t) für konsistentes Verhalten über Plattformen hinweg. In dynamisch typisierten Sprachen verwenden Sie strikte Gleichheitsoperatoren und explizite Typprüfungen. Seien Sie vorsichtig mit Union-Typen - dokumentieren und erzwingen Sie, welches Member gültig ist. Aktivieren Sie Compiler-Warnungen für Typkonvertierungsprobleme und behandeln Sie diese als Fehler. Verwenden Sie statische Analysetools, die gefahrliche Typkonvertierungen erkennen.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Speicher modifizieren - Fehlerhafte Typkonvertierungen können zu Schreibvorgangen über Puffergrenzen hinaus oder zu Speicherkorruption führen.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz, Beenden oder Neustart - Speicherkorruption durch fehlerhafte Casts führt oft zu Absturzen.
AndereBereich: Ändere

Variiert je nach Kontext - Auswirkungen hängen davon ab, wie der fehlerhaft konvertierte Wert verwendet wird.

Beispielcode

Verwundbarer Code

// Verwundbar: Signed-zu-Unsigned-Konvertierung des Rückgabewerts
unsigned int vulnerable_read_data() {
    int amount = 0;

    // accessmainframe() gibt -1 bei Fehler zurück
    amount = accessmainframe();

    // Verwundbar: Rückgabe von int als unsigned int
    // Wenn amount -1 ist, gibt es 4.294.967.295 (UINT_MAX) auf 32-Bit zurück
    return amount;
}

void use_vulnerable() {
    unsigned int size = vulnerable_read_data();

    if (size > 0 && size < 1000000) {  // Dies wird für UINT_MAX bestanden!
        // Tatsächlich ist UINT_MAX > 1000000, also wurde dies fehlschlagen
        // Aber andere Prüfungen könnten unerwartet bestehen
    }

    char* buffer = malloc(size);  // Riesige Allokation oder Overflow
}

// Verwundbar: 64-Bit-zu-32-Bit-Abschneidung (CVE-2021-43537 Muster)
void vulnerable_truncation(uint64_t large_value) {
    // Verwundbar: Erzwungener Cast schneidet obere 32 Bits ab
    uint32_t truncated = (uint32_t)large_value;

    // Wenn large_value = 0x100000010 (4GB + 16)
    // truncated = 16

    char* buffer = malloc(truncated);  // Alloziert nur 16 Bytes
    // Späterer Code verwendet large_value für Pufferzugriff - Overflow!
    fill_buffer(buffer, large_value);
}
// Verwundbar: Union-Typ-Verwechslung
struct MessageBuffer {
    int msgType;
    union {
        char *name;      // Pointer (4 oder 8 Bytes)
        int nameID;      // Integer (4 Bytes)
    };
};

void vulnerable_union_access(struct MessageBuffer *buf) {
    // Zuerst wird name auf einen gültigen Pointer gesetzt
    buf->name = "ValidString";

    // Dann modifiziert der Angreifer nameID
    buf->nameID = 0x41414141;  // Überschreibt Pointer-Wert!

    // Verwundbar: Jetzt wird name als Pointer verwendet, aber er zeigt auf 0x41414141
    printf("Name: %s\n", buf->name);  // Absturz oder willkürliches Lesen

    // Ebenfalls verwundbar: Lesen über Union-Grenzen hinaus
    char* ptr = buf->name;
    ptr[100] = 'X';  // Out-of-bounds-Schreiben wenn Pointer korrumpiert wurde
}
<?php
// Verwundbar: Loser Vergleich Typumwandlung (CVE-2022-3979 Muster)
function vulnerable_validate_hash($user_hash, $stored_hash) {
    // Verwundbar: Loser Vergleich mit !=
    if ($user_hash != $stored_hash) {
        return false;
    }
    return true;
}

// Angriff: Wenn stored_hash = "0e12345678" (wissenschaftliche Notation)
// und Benutzer "0e99999999" liefert (ändere Zahl aber beide gleich 0)
// "0e12345678" == "0e99999999" ergibt TRUE!
// Beide werden als 0 in wissenschaftlicher Notation behandelt

// Verwundbar: Type Juggling bei Authentifizierung
$password = $_GET['password'];
$correct = 0;  // Integer Null

// Verwundbar: Vergleich von String mit Integer mit ==
if ($password == $correct) {
    // "0" == 0 ist TRUE
    // "" == 0 ist TRUE
    // "abc" == 0 ist TRUE (nicht-numerischer String gleich 0)
    grant_access();
}
?>
// Verwundbar: Falscher Cast in Java
public class VulnerableCast {

    public void vulnerableDowncast(Object obj) {
        // Verwundbar: Ungeprüfter Cast kann ClassCastException werfen
        String str = (String) obj;  // Absturz wenn obj kein String ist
        process(str);
    }

    public void vulnerableNumericCast(long bigValue) {
        // Verwundbar: Stille Abschneidung
        int smallValue = (int) bigValue;

        // Wenn bigValue = 2147483648L, smallValue = -2147483648
        // Vorzeichenbit-Interpretation ändert sich komplett!

        if (smallValue > 0) {  // Dies schlagt für große positive Longs fehl
            allocate(smallValue);
        }
    }
}

Lösungscode

// Behoben: Validierung vor Unsigned-Konvertierung
int secure_read_data(unsigned int *result) {
    int amount = accessmainframe();

    // Behoben: Prüfung auf Fehler vor Konvertierung
    if (amount < 0) {
        return -1;  // Fehlerindikator
    }

    *result = (unsigned int)amount;
    return 0;  // Erfolg
}

void use_secure() {
    unsigned int size;

    if (secure_read_data(&size) < 0) {
        // Fehler behandeln
        return;
    }

    if (size == 0 || size > MAX_SIZE) {
        return;
    }

    char* buffer = malloc(size);
}

// Behoben: Validierung vor Abschneidung
int secure_truncation(uint64_t large_value, uint32_t *result) {
    // Behoben: Prüfen ob Wert in 32 Bits passt
    if (large_value > UINT32_MAX) {
        return -1;  // Fehler: Wert zu groß
    }

    *result = (uint32_t)large_value;
    return 0;
}

void use_secure_truncation(uint64_t value) {
    uint32_t truncated;

    if (secure_truncation(value, &truncated) < 0) {
        // Fehler behandeln - Wert passt nicht
        return;
    }

    char* buffer = malloc(truncated);
    fill_buffer(buffer, truncated);  // Gleiche Variable konsistent verwenden
}
// Behoben: Sichere Union-Behandlung mit Typ-Tag
typedef enum { MSG_BY_NAME, MSG_BY_ID } MessageType;

struct SafeMessageBuffer {
    MessageType msgType;
    union {
        char *name;
        int nameID;
    } data;
};

void secure_union_access(struct SafeMessageBuffer *buf) {
    // Behoben: Typ-Tag vor Zugriff auf Union-Member prüfen
    switch (buf->msgType) {
        case MSG_BY_NAME:
            if (buf->data.name != NULL) {
                printf("Name: %s\n", buf->data.name);
            }
            break;

        case MSG_BY_ID:
            printf("ID: %d\n", buf->data.nameID);
            break;

        default:
            // Ungültiger Typ - Fehler behandeln
            break;
    }
}

// Behoben: Union-Zugriff kapseln
void set_message_name(struct SafeMessageBuffer *buf, char *name) {
    buf->msgType = MSG_BY_NAME;
    buf->data.name = name;
}

void set_message_id(struct SafeMessageBuffer *buf, int id) {
    buf->msgType = MSG_BY_ID;
    buf->data.nameID = id;
}
<?php
// Behoben: Strikter Vergleich
function secure_validate_hash($user_hash, $stored_hash) {
    // Behoben: Strikten Vergleich === verwenden
    if ($user_hash !== $stored_hash) {
        return false;
    }
    return true;
}

// Noch besser: Zeitkonstanten Vergleich verwenden
function secure_validate_hash_timing_safe($user_hash, $stored_hash) {
    return hash_equals($stored_hash, $user_hash);
}

// Behoben: Typsichere Passwortprüfung
$password = $_GET['password'];
$correct_hash = '$2y$10$...';  // Gespeicherter Passwort-Hash

// Behoben: password_verify für sicheren Vergleich verwenden
if (password_verify($password, $correct_hash)) {
    grant_access();
}

// Behoben: Explizite Typvalidierung
function secure_process($value) {
    // Behoben: Typ explizit validieren
    if (!is_string($value)) {
        throw new InvalidArgumentException("String erwartet");
    }

    // Jetzt sicher als String verwendbar
    return trim($value);
}
?>
// Behoben: Sicherer Cast mit instanceof-Prüfung
public class SecureCast {

    public void secureDowncast(Object obj) {
        // Behoben: Typ vor Cast prüfen
        if (obj instanceof String) {
            String str = (String) obj;
            process(str);
        } else {
            handleInvalidType(obj);
        }
    }

    public int secureNumericCast(long bigValue) throws ArithmeticException {
        // Behoben: Math.toIntExact verwenden, das bei Overflow wirft
        return Math.toIntExact(bigValue);
    }

    // Alternative: Manuelle Bereichsprüfung
    public int secureNumericCastManual(long bigValue) {
        if (bigValue < Integer.MIN_VALUE || bigValue > Integer.MAX_VALUE) {
            throw new ArithmeticException("Wert außerhalb des int-Bereichs: " + bigValue);
        }
        return (int) bigValue;
    }
}

CVE-Beispiele

  • CVE-2021-43537: Unsigned 64-Bit-zu-32-Bit-Cast verursachte Integer-Overflow und Heap-Speicherkorruption im Browser.
  • CVE-2022-3979: PHP loser Vergleich (!=) statt strikt (!==) ermöglichte Hash-Validierungsumgehung.
  • CVE-2009-0231: Integer-Abschneidung verursachte Heap-Buffer-Overflow.

Referenzen

  1. MITRE Corporation. "CWE-704: Incorrect Type Conversion or Cast." https://cwe.mitre.org/data/definitions/704.html
  2. CERT C Coding Standard. "INT31-C. Ensure that integer conversions do not result in löst or misinterpreted data."
  3. OWASP. "Type Juggling Vulnerabilities."