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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Vertraulichkeit Schutzmechanismus umgehen - Angreifer können Timing-Analyse verwenden, um korrekte Werte inkrementell abzuleiten. |
| Autorisierung | Umfang: 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
- MITRE Corporation. "CWE-1254: Incorrect Comparison Logic Granularity." https://cwe.mitre.org/data/definitions/1254.html
- Brumley & Boneh. "Remote Timing Attacks are Practical"
- Percival, Colin. "Cache Missing for Fun and Profit"