Verdeckter Timing-Kanal

Beschreibung

Verdeckter Timing-Kanal ist eine Schwachstelle, die auftritt, wenn sensible Informationen durch Beobachtung des Timings von Systemoperationen abgeleitet werden können. Diese Kanäle übermitteln Informationen, indem sie einen Aspekt des Systemverhaltens über die Zeit modulieren, sodass ein Angreifer, der das Timing überwacht, geschützte Informationen ableiten kann. Häufige Beispiele sind kryptographische Operationen, die je nach Eingabe oder Schlüsselmaterial unterschiedlich lange dauern, Passwortvalidierung, die bei Nichtübereinstimmung früh zurückkehrt, und Datenbankabfragen, deren Ausführungszeit je nach zugegriffenen Daten variiert.

Risiko

Timing-Kanäle können kryptographische Schlüssel, Passwörter und andere sensible Daten offenlegen. Angreifer können gültige Benutzernamen ermitteln, indem sie Antwortzeiten für gültige versus ungültige Konten vergleichen. Kryptographische Implementierungen, die für Timing-Angriffe anfällig sind, können ihre Schlüssel durch statistische Analyse des Operations-Timings extrahiert bekommen. Datenbankabfragen können Informationen über Datenexistenz und -inhalt durch Analyse der Abfrageausführungszeit preisgeben. Diese Angriffe sind besonders gefährlich, weil sie keine Spur in Anwendungsprotokollen hinterlassen und aus der Ferne durchgeführt werden können. Moderne CPU-Funktionen wie Sprungvorhersage und Caching machen Timing-Angriffe praktischer.

Lösung

Entwerfen Sie Implementierungen, die Zeitvarianzen in sicherheitsrelevanten Operationen eliminieren. Verwenden Sie Konstantzeit-Vergleichsfunktionen für Passwörter, MACs und andere Geheimnisse. Fügen Sie künstliche oder zufällige Verzögerungen hinzu, sodass die verbrauchte CPU-Zeit unabhängig von der durchgeführten Aktion ist. Verwenden Sie kryptographische Bibliotheken, die Konstantzeit-Operationen implementieren. Vermeiden Sie Early-Return-Muster in Authentifizierungscode. Erwägen Sie das Hinzufügen von zufälligem Jitter, um Timing-Informationen zu maskieren. Stellen Sie für besonders sensible Operationen sicher, dass die Ausführungszeit unabhängig vom genommenen Codepfad einheitlich ist.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitUmfang: Vertraulichkeit

Informationsoffenlegung durch die Fähigkeit, Anwendungsdaten durch Analyse des Operations-Timings zu lesen.

Beispielcode

Anfälliger Code

// Anfällig: Frühe Rückgabe bei Passwort-Nichtübereinstimmung
int vulnerable_check_password(const char *provided, const char *stored) {
    size_t provided_len = strlen(provided);
    size_t stored_len = strlen(stored);

    // Anfällig: Längenvergleich verrät Passwortlänge
    if (provided_len != stored_len) {
        return 0;  // Frühe Rückgabe
    }

    // Anfällig: Zeichen-für-Zeichen-Vergleich
    for (size_t i = 0; i < stored_len; i++) {
        if (provided[i] != stored[i]) {
            return 0;  // Frühe Rückgabe verrät Position der Nichtübereinstimmung
        }
    }

    return 1;
}
# Anfällig: Timing-basierte Benutzernamen-Enumeration
def vulnerable_login(username, password):
    user = database.find_user(username)

    if user is None:
        # Anfällig: Schnelle Rückgabe für ungültigen Benutzernamen
        return False

    # Langsame Hash-Berechnung nur für gültige Benutzernamen
    hashed = hash_password(password, user.salt)

    if hashed == user.password_hash:
        return True

    return False

# Anfällig: String-Vergleich
def vulnerable_verify_token(provided, expected):
    # Anfällig: Frühe Beendigung verrät Übereinstimmungsposition
    return provided == expected
// Anfällig: MAC-Verifikation mit Timing-Leck
public class VulnerableMacVerifier {

    public boolean verifyMac(byte[] message, byte[] providedMac, SecretKey key) {
        byte[] computedMac = computeMac(message, key);

        // Anfällig: Arrays.equals kehrt früh bei Nichtübereinstimmung zurück
        return Arrays.equals(computedMac, providedMac);
    }

    public boolean verifySignature(byte[] data, byte[] signature) {
        byte[] expected = computeSignature(data);

        // Anfällig: Byte-für-Byte-Vergleich leckt Informationen
        if (signature.length != expected.length) {
            return false;  // Längen-Leck
        }

        for (int i = 0; i < expected.length; i++) {
            if (signature[i] != expected[i]) {
                return false;  // Positions-Leck
            }
        }
        return true;
    }
}

Korrigierter Code

// Korrigiert: Konstantzeit-Passwortvergleich
#include <stdint.h>
#include <string.h>

int secure_check_password(const char *provided, const char *stored,
                          size_t max_len) {
    volatile uint8_t result = 0;
    size_t i;

    // Korrigiert: Immer max_len Bytes verarbeiten
    for (i = 0; i < max_len; i++) {
        // XOR akkumuliert Unterschiede ohne frühen Ausstieg
        result |= provided[i] ^ stored[i];
    }

    // Korrigiert: Zusätzliche Prüfung für Null-Terminatoren
    // Dies ist konstantzeitig, weil beide Strings verarbeitet werden

    return result == 0;
}

// Korrigiert: OpenSSLs Konstantzeit-Vergleich verwenden
#include <openssl/crypto.h>

int secure_compare(const void *a, const void *b, size_t len) {
    // Korrigiert: CRYPTO_memcmp ist konstantzeitig
    return CRYPTO_memcmp(a, b, len) == 0;
}
# Korrigiert: Konstantzeit-Vergleich mit hmac.compare_digest
import hmac
import secrets
import time

def secure_login(username, password):
    user = database.find_user(username)

    # Korrigiert: Hash immer berechnen, auch für ungültigen Benutzernamen
    if user is None:
        # Dummy-Werte für Timing-Konsistenz verwenden
        dummy_salt = secrets.token_bytes(16)
        dummy_hash = hash_password(password, dummy_salt)
        # Gegen Zufallswert vergleichen um Timing beizubehalten
        hmac.compare_digest(dummy_hash, secrets.token_bytes(len(dummy_hash)))
        return False

    hashed = hash_password(password, user.salt)

    # Korrigiert: Konstantzeit-Vergleich
    if hmac.compare_digest(hashed, user.password_hash):
        return True

    return False

# Korrigiert: Konstantzeit-Token-Verifikation
def secure_verify_token(provided, expected):
    # Korrigiert: hmac.compare_digest ist konstantzeitig
    if len(provided) != len(expected):
        # Korrigiert: Trotzdem Vergleich durchführen um Timing-Leck zu vermeiden
        # Gegen sich selbst vergleichen um gleiche Zeit zu verbrauchen
        hmac.compare_digest(provided, provided)
        return False

    return hmac.compare_digest(provided.encode(), expected.encode())

# Korrigiert: Künstliche Verzögerung für zusätzlichen Schutz
def secure_login_with_delay(username, password):
    start_time = time.monotonic()

    result = secure_login(username, password)

    # Korrigiert: Mindestoperationszeit sicherstellen
    elapsed = time.monotonic() - start_time
    min_time = 0.1  # 100ms Minimum

    if elapsed < min_time:
        time.sleep(min_time - elapsed)

    return result
// Korrigiert: Konstantzeit-MAC-Verifikation
import java.security.MessageDigest;

public class SecureMacVerifier {

    public boolean verifyMac(byte[] message, byte[] providedMac, SecretKey key) {
        byte[] computedMac = computeMac(message, key);

        // Korrigiert: MessageDigest.isEqual ist konstantzeitig
        return MessageDigest.isEqual(computedMac, providedMac);
    }

    // Korrigiert: Manueller Konstantzeit-Vergleich
    public static boolean constantTimeEquals(byte[] a, byte[] b) {
        if (a == null || b == null) {
            return a == b;
        }

        if (a.length != b.length) {
            // Korrigiert: Trotzdem vergleichen um Längen-Timing-Leck zu vermeiden
            // a gegen sich selbst vergleichen
            int dummy = 0;
            for (int i = 0; i < a.length; i++) {
                dummy |= a[i] ^ a[i];
            }
            return false;
        }

        // Korrigiert: Alle Unterschiede akkumulieren
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result |= a[i] ^ b[i];
        }

        return result == 0;
    }

    // Korrigiert: Konstantzeit-String-Vergleich
    public static boolean constantTimeStringEquals(String a, String b) {
        if (a == null || b == null) {
            return a == b;
        }

        byte[] aBytes = a.getBytes(StandardCharsets.UTF_8);
        byte[] bBytes = b.getBytes(StandardCharsets.UTF_8);

        return MessageDigest.isEqual(aBytes, bBytes);
    }
}

CVE-Beispiele

Keine spezifischen CVEs sind für diese CWE gelistet. Das Schwachstellenmuster erscheint in:

  • Kryptographischen Implementierungen (Timing-Angriffe auf AES, RSA)
  • Passwort-Verifikationssystemen
  • HMAC- und digitaler Signatur-Verifikation

Referenzen

  1. MITRE Corporation. "CWE-385: Covert Timing Channel." https://cwe.mitre.org/data/definitions/385.html
  2. Kocher, Paul. "Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems."