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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: 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
- MITRE Corporation. "CWE-385: Covert Timing Channel." https://cwe.mitre.org/data/definitions/385.html
- Kocher, Paul. "Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems."