Unerwarteter Statuscode oder Rückgabewert
Beschreibung
Unerwarteter Statuscode oder Rückgabewert ist eine Schwachstelle, die auftritt, wenn Software nicht ausreichend überprüft, ob eine Funktion oder Operation einen legitimen Wert zurückgibt, den das Produkt nicht zu empfangen erwartet hatte. Obwohl verwandt mit ungeprüften Rückgabewerten (CWE-252), betrifft diese Schwachstelle speziell Fälle, in denen der Code einige Rückgabewerte prüft, aber unerwartete, aber gültige Werte nicht behandelt. Das Programm kann von einer begrenzten Menge möglicher Rückgaben ausgehen, während die API tatsächlich zusätzliche legitime Statuscodes zurückgeben kann.
Risiko
Das Nichtbehandeln unerwarteter, aber gültiger Rückgabewerte kann dazu führen, dass Programme in undefinierte Zustände geraten. Netzwerkfunktionen wie recv() können weniger Bytes als erwartet oder spezielle Werte zurückgeben, die verschiedene Bedingungen anzeigen. DNS-Lookups können unerwartete Ergebnisse zurückgeben, die Zugriffskontrollen umgehen. Systemaufrufe können sich auf verschiedenen Plattformen oder Hardware unterschiedlich verhalten und Werte zurückgeben, die vom Code nicht behandelt werden. Wenn diese unerwarteten Werte nicht behandelt werden, können Programme in Endlosschleifen geraten, Speicher leaken, Daten uninitialisiert lassen oder Sicherheitsprüfungen umgehen. Die Schwachstelle ist besonders gefährlich, weil der Code den Anschein erweckt, Rückgabewerte zu prüfen, aber Grenzfälle übersieht.
Lösung
Überprüfen Sie die API-Dokumentation gründlich, um alle möglichen Rückgabewerte zu verstehen, nicht nur die häufigen. Behandeln Sie alle dokumentierten Rückgabewerte explizit, einschließlich Grenzfälle und plattformspezifisches Verhalten. Verwenden Sie defensive Programmiertechniken, die unerwartete Werte als Fehler behandeln, anstatt Erfolg anzunehmen. Implementieren Sie ordnungsgemäße Bereichsprüfungen, wenn Funktionen Zähler oder Größen zurückgeben. Testen Sie mit Fuzzing und Grenzfalltests, um Codepfade zu entdecken, die ungewöhnliche Rückgaben nicht behandeln. Erwägen Sie die Verwendung von Wrapper-Funktionen, die Rückgabewerte plattformübergreifend normalisieren.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität, Sonstiges Unerwarteter Zustand, veränderte Ausführungslogik -- Das Programm kann in unbeabsichtigte Zustände geraten oder unbeabsichtigte Logik ausführen, wenn unerwartete Werte empfangen werden. |
Beispielcode und Lösung
Verwundbarer Code
// VERWUNDBAR: Behandelt nicht alle recv()-Rückgabewerte
void vulnerable_receive_data(int sock, char *buffer, size_t expected) {
ssize_t received = recv(sock, buffer, expected, 0);
// VERWUNDBAR: Prüft nur auf Fehler (-1)
if (received == -1) {
perror("recv failed");
return;
}
// VERWUNDBAR: Behandelt nicht 0 (Verbindung geschlossen)
// VERWUNDBAR: Behandelt kein partielles Lesen (received < expected)
// Fährt fort, als ob alle Daten empfangen wurden
process_data(buffer, expected); // Kann uninitialisierten Speicher verwenden!
}
// VERWUNDBAR: Behandelt nicht unerwartete read()-Rückgaben
void vulnerable_read_file(int fd, char *buffer, size_t size) {
ssize_t bytes_read = read(fd, buffer, size);
if (bytes_read > 0) {
// VERWUNDBAR: Nimmt an, alle Bytes wurden gelesen
buffer[size - 1] = '\0'; // Kann Daten uninitialisiert lassen
}
}
// VERWUNDBAR: Unerwarteter recvfrom-Rückgabewert bewirkt Schleifenabbruch
void vulnerable_server_loop(int sock) {
char buffer[1024];
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
while (1) {
ssize_t received = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client, &client_len);
// VERWUNDBAR: Null-Länge-Paket verursacht unerwartetes Verhalten
if (received <= 0) {
// Behandelt 0 wie Fehler, kann Schleife unerwartet beenden
break;
}
handle_request(buffer, received, &client);
}
}
# VERWUNDBAR: Unerwartetes DNS-Lookup-Ergebnis
def vulnerable_access_check(hostname):
try:
ip = socket.gethostbyname(hostname)
except socket.gaierror:
# Blockiert - Hostname wird nicht aufgelöst
return False
# VERWUNDBAR: Behandelt nicht leeres Ergebnis oder unerwartetes Format
if ip is None:
return False
# VERWUNDBAR: Reverse-Lookup kann None zurückgeben
reverse = socket.gethostbyaddr(ip)[0]
# VERWUNDBAR: Wenn Reverse-Lookup fehlschlägt/unerwarteten Wert zurückgibt,
# wird diese Prüfung umgangen
if reverse != expected_hostname:
return False
return True
# VERWUNDBAR: Partielles Lesen nicht behandelt
def vulnerable_read_message(sock):
# Erwartet genau 4 Bytes für die Nachrichtenlänge
length_data = sock.recv(4)
# VERWUNDBAR: recv() kann weniger als 4 Bytes zurückgeben
length = struct.unpack('!I', length_data)[0] # Kann fehlschlagen oder falschen Wert liefern!
# Nachricht lesen
message = sock.recv(length) # Kann ebenfalls partiell sein!
return message
// VERWUNDBAR: InputStream.read() partielles Lesen
public class VulnerableReader {
public byte[] readExact(InputStream in, int size) throws IOException {
byte[] buffer = new byte[size];
// VERWUNDBAR: read() kann weniger Bytes als angefordert zurückgeben
int bytesRead = in.read(buffer);
if (bytesRead == -1) {
throw new EOFException("Unexpected end of stream");
}
// VERWUNDBAR: Nimmt an, alle Bytes wurden gelesen
return buffer; // Kann uninitialisierte Daten enthalten!
}
}
Sichere Lösung
// SICHER: Behandelt alle recv()-Rückgabewerte
int secure_receive_data(int sock, char *buffer, size_t expected) {
size_t total_received = 0;
while (total_received < expected) {
ssize_t received = recv(sock, buffer + total_received,
expected - total_received, 0);
// SICHER: Fehler behandeln
if (received == -1) {
perror("recv failed");
return -1;
}
// SICHER: Geschlossene Verbindung behandeln
if (received == 0) {
fprintf(stderr, "Connection closed by peer\n");
return -2;
}
// SICHER: Partielles Lesen durch Schleife behandeln
total_received += received;
}
process_data(buffer, expected);
return 0;
}
// SICHER: Behandelt alle read()-Szenarien
int secure_read_file(int fd, char *buffer, size_t size) {
size_t total_read = 0;
while (total_read < size) {
ssize_t bytes_read = read(fd, buffer + total_read, size - total_read);
if (bytes_read == -1) {
if (errno == EINTR) {
continue; // Unterbrochen, erneut versuchen
}
return -1; // Tatsächlicher Fehler
}
if (bytes_read == 0) {
// EOF erreicht vor erwarteten Bytes
return -2;
}
total_read += bytes_read;
}
return 0; // Erfolg
}
// SICHER: Behandelt Null-Länge-Pakete korrekt
void secure_server_loop(int sock) {
char buffer[1024];
struct sockaddr_in client;
socklen_t client_len;
while (1) {
client_len = sizeof(client); // Jedes Mal zurücksetzen
ssize_t received = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client, &client_len);
if (received == -1) {
if (errno == EINTR) {
continue; // Unterbrochen, erneut versuchen
}
perror("recvfrom failed");
break; // Tatsächlicher Fehler
}
// SICHER: Null-Länge-Paket ist gültig, einfach Verarbeitung überspringen
if (received == 0) {
continue;
}
handle_request(buffer, received, &client);
}
}
# SICHER: Behandelt alle DNS-Lookup-Ergebnisse
def secure_access_check(hostname):
try:
ip = socket.gethostbyname(hostname)
except socket.gaierror as e:
logger.warning(f"DNS lookup failed for {hostname}: {e}")
return False
if not ip:
return False
try:
reverse_result = socket.gethostbyaddr(ip)
reverse = reverse_result[0] if reverse_result else None
except socket.herror:
# SICHER: Behandelt Reverse-Lookup-Fehler als Zugriff verweigert
logger.warning(f"Reverse DNS failed for {ip}")
return False
if reverse is None or reverse != expected_hostname:
return False
return True
# SICHER: Behandelt partielles Lesen
def secure_read_message(sock):
# Genau 4 Bytes für die Länge lesen
length_data = secure_recv_exact(sock, 4)
if length_data is None:
raise ConnectionError("Failed to read message length")
length = struct.unpack('!I', length_data)[0]
# Länge validieren
if length > MAX_MESSAGE_SIZE:
raise ValueError(f"Message too large: {length}")
# Exakte Nachrichtenlänge lesen
message = secure_recv_exact(sock, length)
if message is None:
raise ConnectionError("Failed to read message body")
return message
def secure_recv_exact(sock, size):
"""Empfängt genau 'size' Bytes."""
buffer = b''
while len(buffer) < size:
chunk = sock.recv(size - len(buffer))
if not chunk:
return None # Verbindung geschlossen
buffer += chunk
return buffer
// SICHER: Behandelt partielles Lesen von InputStream
public class SecureReader {
public byte[] readExact(InputStream in, int size) throws IOException {
byte[] buffer = new byte[size];
int totalRead = 0;
while (totalRead < size) {
int bytesRead = in.read(buffer, totalRead, size - totalRead);
// SICHER: EOF behandeln
if (bytesRead == -1) {
throw new EOFException(
"Expected " + size + " bytes, got " + totalRead);
}
totalRead += bytesRead;
}
return buffer;
}
// Alternative: DataInputStream verwenden, der dies behandelt
public byte[] readExactWithDataStream(InputStream in, int size)
throws IOException {
DataInputStream dis = new DataInputStream(in);
byte[] buffer = new byte[size];
dis.readFully(buffer); // Wirft EOFException wenn nicht genug Bytes
return buffer;
}
}
CVE-Beispiele
- CVE-2004-1395 -- Null-Byte- und andere Längenpakete verursachen bei recvfrom einen unerwarteten Rückgabecode, der die Empfangsschleife des Servers beendet.
- CVE-2002-2124 -- Ungeprüfter recv()-Rückgabecode löst eine Endlosschleife aus.
- CVE-2005-1858 -- read(), das weniger Bytes als erwartet zurückgibt, lässt Speicher ungelöscht.
- CVE-2000-0536 -- Zugriffsbeschränkung wird umgangen, wenn DNS-Reverse-Lookup keinen Hostnamen zurückgibt.
Referenzen
- MITRE Corporation. "CWE-394: Unexpected Status Code or Return Value." https://cwe.mitre.org/data/definitions/394.html