Fehlerhafte Granularität der Vergleichslogik

Beschreibung

Fehlerhafte Granularität der Vergleichslogik tritt auf, wenn ein Produkt Vergleichsoperationen über mehrere Schritte statt als eine einzige einheitliche Operation durchführt. Diese Schwachstelle betrifft sicherheitskritische Vergleiche mit Passwörtern, Message Authentication Codes (MACs) und Challenge-Response-Überprüfungen. Byte-für-Byte- oder schrittweise Vergleichsimplementierungen, die bei Nichtübereinstimmung vorzeitig abbrechen, erzeugen ausnutzbare Timing-Seitenkanale. Wenn der Vergleich bei einem Zwischenschritt fehlschlägt, können Angreifer Timing-Unterschiede ausnutzen, um genau zu identifizieren, wo der Fehler aufgetreten ist, und so potenziell Authentifizierungs- oder Verifizierungsmechanismen kompromittieren.

Risiko

Fehlerhafte Vergleichsgranularität hat schwerwiegende Sicherheitsauswirkungen. Timing-Seitenkanale offenbaren teilweise geheime Informationen. Passwortvergleiche geben Zeichenpositionen preis. MAC-Überprüfungen werden anfällig für inkrementelle Angriffe. Challenge-Response-Systeme können Byte für Byte besiegt werden. Kryptografische Signaturen können durch Timing-Analyse gefälscht werden. Authentifizierungssysteme werden anfällig für Brute-Force-Optimierung. Mehrere Versuche ermöglichen vollständige Geheimnis-Wiederherstellung. Sicherheitstoken können ohne vollständige Schlüsselkenntnis bestimmt werden.

Lösung

Stellen Sie sicher, dass Vergleichslogik implementiert wird, um in einer Operation statt in kleineren Teilen zu vergleichen. Verwenden Sie Konstant-Zeit-Vergleichsfunktionen. Vergleichen Sie alle Bytes unabhängig von Zwischenergebnissen. Vermeiden Sie vorzeitigen Abbruch bei Nichtübereinstimmung. Verwenden Sie bitweises ODER zur Akkumulation von Unterschieden. Implementieren Sie Festzeit-Algorithmen für alle Sicherheitsvergleiche. Fügen Sie Timing-Rauschen nur als ergänzenden Schutz hinzu. Verwenden Sie hardware-unterstützte Konstant-Zeit-Operationen wo verfügbar.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitUmfang: Vertraulichkeit

Schutzmechanismus umgehen - Angreifer können Timing-Analyse verwenden, um korrekte Werte inkrementell abzuleiten.
AutorisierungUmfang: Autorisierung

Schutzmechanismus umgehen - Authentifizierungskontrollen können durch Timing-Messungen umgangen werden.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Passwortvergleich mit vorzeitigem Abbruch

#include <stdint.h>
#include <string.h>

bool vulnerable_password_check(const char* input, const char* stored, size_t len) {
    // VERWUNDBAR: Gibt sofort bei erster Nichtübereinstimmung zurück
    // Timing offenbart Position des ersten falschen Zeichens
    for (size_t i = 0; i < len; i++) {
        if (input[i] != stored[i]) {
            return false;  // <-- Vorzeitiger Abbruch leakt Timing
        }
    }
    return true;
}

// VERWUNDBAR: strcmp bricht ebenfalls vorzeitig ab
bool vulnerable_strcmp_check(const char* input, const char* password) {
    return strcmp(input, password) == 0;  // Vorzeitiger Abbruch
}

// VERWUNDBAR: Byte-für-Byte MAC-Überprüfung
bool vulnerable_mac_verify(const uint8_t* computed, const uint8_t* received, size_t len) {
    for (size_t i = 0; i < len; i++) {
        if (computed[i] != received[i]) {
            return false;  // VERWUNDBAR: Timing offenbart MAC-Byte-Positionen
        }
    }
    return true;
}

// Timing-Angriff-Beispiel
// Angreifer misst: Passwort "AAAA" - 100ns (schlägt an Position 0 fehl)
// Angreifer misst: Passwort "PAAA" - 150ns (schlägt an Position 1 fehl)
// Erstes Zeichen ist 'P'
// Für jede Position fortfahren, um vollständiges Passwort wiederherzustellen
// VERWUNDBAR: Sequentieller Vergleich in Hardware

module vulnerable_token_compare (
    input wire clk,
    input wire reset_n,
    input wire start,
    input wire [63:0] input_token,
    input wire [63:0] stored_token,
    output reg match,
    output reg done
);

    reg [2:0] byte_index;
    reg [2:0] state;

    parameter IDLE = 3'h0;
    parameter COMPARE = 3'h1;
    parameter DONE_MATCH = 3'h2;
    parameter DONE_FAIL = 3'h3;

    // VERWUNDBAR: Byte-für-Byte-Vergleich mit vorzeitigem Abbruch
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            state <= IDLE;
            byte_index <= 3'h0;
            match <= 1'b0;
            done <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    if (start) begin
                        state <= COMPARE;
                        byte_index <= 3'h0;
                        done <= 1'b0;
                    end
                end

                COMPARE: begin
                    // VERWUNDBAR: Ein Byte zur Zeit prüfen
                    if (input_token[byte_index*8 +: 8] !=
                        stored_token[byte_index*8 +: 8]) begin
                        // Vorzeitiger Abbruch - Timing offenbart Byte-Position
                        state <= DONE_FAIL;
                    end
                    else if (byte_index == 3'h7) begin
                        state <= DONE_MATCH;
                    end
                    else begin
                        byte_index <= byte_index + 1;
                    end
                end

                DONE_MATCH: begin
                    match <= 1'b1;
                    done <= 1'b1;
                    state <= IDLE;
                end

                DONE_FAIL: begin
                    match <= 1'b0;
                    done <= 1'b1;
                    // Timing: done-Signal kommt zu unterschiedlichen Zeiten
                    // basierend darauf, welches Byte fehlschlug
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule
# VERWUNDBAR: Python-Vergleich mit vorzeitigem Abbruch

def vulnerable_hmac_verify(expected: bytes, received: bytes) -> bool:
    """VERWUNDBAR: Vorzeitiger Abbruch offenbart HMAC-Bytes"""
    if len(expected) != len(received):
        return False

    for i in range(len(expected)):
        if expected[i] != received[i]:
            return False  # VERWUNDBAR: Leakt Position
    return True

def vulnerable_api_key_check(provided: str, valid: str) -> bool:
    """VERWUNDBAR: String-Vergleich mit Timing-Leak"""
    return provided == valid  # Python == kann kurzschließen

# Angriffsdemonstration
import time

def timing_attack(target_function, known_prefix, charset, length):
    """Timing ausnutzen, um Geheimnis Byte für Byte wiederherzustellen"""
    secret = known_prefix

    for position in range(len(known_prefix), length):
        best_time = 0
        best_char = None

        for char in charset:
            test = secret + char + 'X' * (length - position - 1)

            start = time.perf_counter_ns()
            target_function(test)
            elapsed = time.perf_counter_ns() - start

            if elapsed > best_time:
                best_time = elapsed
                best_char = char

        secret += best_char
        print(f"Position {position}: '{best_char}' (wiederhergestellt: {secret})")

    return secret

Sichere Lösung

// SICHER: Konstant-Zeit-Vergleich

#include <stdint.h>
#include <string.h>

bool secure_password_check(const char* input, const char* stored, size_t len) {
    // SICHER: Konstant-Zeit-Vergleich
    volatile uint8_t result = 0;

    // Immer alle Bytes vergleichen
    for (size_t i = 0; i < len; i++) {
        // XOR akkumuliert Unterschiede ohne Verzweigung
        result |= input[i] ^ stored[i];
    }

    // Einzelner Vergleich am Ende
    return result == 0;
}

// SICHER: Konstant-Zeit-Speichervergleich
int secure_memcmp(const void* a, const void* b, size_t len) {
    const volatile uint8_t* pa = (const volatile uint8_t*)a;
    const volatile uint8_t* pb = (const volatile uint8_t*)b;
    volatile uint8_t diff = 0;

    // SICHER: Alle Bytes vergleichen, kein vorzeitiger Abbruch
    for (size_t i = 0; i < len; i++) {
        diff |= pa[i] ^ pb[i];
    }

    // 0 zurückgeben wenn gleich, ungleich Null sonst
    return diff;
}

// SICHER: Konstant-Zeit MAC-Überprüfung
bool secure_mac_verify(const uint8_t* computed, const uint8_t* received, size_t len) {
    volatile uint8_t result = 0;

    // SICHER: Alle Bytes unabhängig von Zwischenergebnissen prüfen
    for (size_t i = 0; i < len; i++) {
        result |= computed[i] ^ received[i];
    }

    return result == 0;
}

// SICHER: Doppelter HMAC-Vergleich (verhindert auch Length Extension)
bool secure_hmac_verify(const uint8_t* key, size_t key_len,
                        const uint8_t* message, size_t msg_len,
                        const uint8_t* received_mac) {
    uint8_t computed_mac[32];
    uint8_t double_computed[32];
    uint8_t double_received[32];

    // Erwarteten MAC berechnen
    hmac_sha256(key, key_len, message, msg_len, computed_mac);

    // SICHER: Doppelter HMAC für Konstant-Zeit-Vergleich
    hmac_sha256(key, key_len, computed_mac, 32, double_computed);
    hmac_sha256(key, key_len, received_mac, 32, double_received);

    return secure_memcmp(double_computed, double_received, 32) == 0;
}
// SICHER: Einzeltakt-Parallelvergleich

module secure_token_compare (
    input wire clk,
    input wire reset_n,
    input wire start,
    input wire [63:0] input_token,
    input wire [63:0] stored_token,
    output reg match,
    output reg done
);

    // SICHER: Alle Bytes parallel in einem Takt vergleichen
    wire [63:0] xor_result;
    wire all_match;

    // Paralleles XOR aller Bits
    assign xor_result = input_token ^ stored_token;

    // Einzelner Vergleich: alle Bits müssen Null sein
    assign all_match = (xor_result == 64'h0);

    // SICHER: Festes Timing unabhängig von Übereinstimmung/Nichtübereinstimmung
    reg [3:0] delay_counter;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            match <= 1'b0;
            done <= 1'b0;
            delay_counter <= 4'h0;
        end
        else if (start) begin
            // Vergleich mit fester Verzögerung starten
            delay_counter <= 4'hF;  // Feste Verzögerungszyklen
            done <= 1'b0;
        end
        else if (delay_counter > 0) begin
            delay_counter <= delay_counter - 1;

            if (delay_counter == 1) begin
                // SICHER: Ergebnis nach fester Verzögerung ausgeben
                match <= all_match;
                done <= 1'b1;
            end
        end
        else begin
            done <= 1'b0;
        end
    end

endmodule

// SICHER: Konstant-Zeit-Vergleich mit zusätzlichem Schutz
module secure_token_compare_enhanced (
    input wire clk,
    input wire reset_n,
    input wire start,
    input wire [127:0] input_token,
    input wire [127:0] stored_token,
    output reg match,
    output reg done
);

    // SICHER: Mehrere parallele Vergleiche für Redundanz
    wire [127:0] xor_result;
    wire [127:0] and_result;
    wire [127:0] or_check;

    assign xor_result = input_token ^ stored_token;
    assign and_result = input_token & stored_token;
    assign or_check = input_token | stored_token;

    // SICHER: Mehrere Prüfungen kombinieren
    wire primary_match = (xor_result == 128'h0);

    // Sekundäre Überprüfung mit anderer Logik
    wire secondary_match = ((and_result ^ or_check) == (input_token ^ stored_token));

    // Beide müssen übereinstimmen
    wire final_match = primary_match && secondary_match;

    // Feste Verzögerung Zustandsautomat
    reg [4:0] timer;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            match <= 1'b0;
            done <= 1'b0;
            timer <= 5'h0;
        end
        else if (start) begin
            timer <= 5'h1F;  // Feste 31-Zyklen-Verzögerung
            done <= 1'b0;
        end
        else if (timer > 0) begin
            timer <= timer - 1;
            if (timer == 1) begin
                match <= final_match;
                done <= 1'b1;
            end
        end
    end

endmodule
# SICHER: Konstant-Zeit-Vergleich in Python

import hmac
import secrets

def secure_compare(a: bytes, b: bytes) -> bool:
    """SICHER: Konstant-Zeit-Vergleich mit hmac.compare_digest"""
    # Längenprüfung mit Konstant-Zeit-Padding
    if len(a) != len(b):
        # Kürzeres auffüllen, um längenbasiertes Timing zu verhindern
        max_len = max(len(a), len(b))
        a = a.ljust(max_len, b'\x00')
        b = b.ljust(max_len, b'\x00')

    # SICHER: Bibliotheks-Konstant-Zeit-Vergleich verwenden
    return hmac.compare_digest(a, b)

def secure_api_key_check(provided: str, valid: str) -> bool:
    """SICHER: Konstant-Zeit-String-Vergleich"""
    # In Bytes konvertieren für Konstant-Zeit-Vergleich
    return hmac.compare_digest(
        provided.encode('utf-8'),
        valid.encode('utf-8')
    )

def secure_hmac_verify(key: bytes, message: bytes, received_mac: bytes) -> bool:
    """SICHER: Konstant-Zeit HMAC-Überprüfung"""
    computed_mac = hmac.new(key, message, 'sha256').digest()

    # SICHER: Konstant-Zeit-Vergleich verwenden
    return hmac.compare_digest(computed_mac, received_mac)

# Manueller Konstant-Zeit-Vergleich (lehrreich)
def constant_time_compare_manual(a: bytes, b: bytes) -> bool:
    """Manueller Konstant-Zeit-Vergleich"""
    if len(a) != len(b):
        return False

    result = 0
    for x, y in zip(a, b):
        # XOR akkumuliert Unterschiede
        result |= x ^ y

    # Einzelner abschließender Vergleich
    return result == 0

CVE-Beispiele

  • CVE-2019-10482: Nicht-konstant-Zeit Smartphone-OS-Vergleiche ermöglichten Timing-Angriffe
  • CVE-2019-10071: Java-Framework verwendet String.equals() für HMAC-Validierung
  • CVE-2014-0984: Router-Passwortfunktion bricht bei erster Nichtübereinstimmung ab

Verwandte CWEs

  • CWE-208: Beobachtbare Timing-Diskrepanz (übergeordnet)
  • CWE-697: Fehlerhafter Vergleich (übergeordnet)
  • CWE-1261: Unsachgemäße Behandlung von Single Event Upsets (gleichrangig)
  • CWE-1255: Vergleichslogik anfällig für Power-Seitenkanal-Angriffe (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1254: Incorrect Comparison Logic Granularity." https://cwe.mitre.org/data/definitions/1254.html
  2. Brumley & Boneh. "Remote Timing Attacks are Practical"
  3. Percival, Colin. "Cache Missing for Fun and Profit"