Rückgabe eines falschen Statuscodes
Beschreibung
Rückgabe eines falschen Statuscodes ist eine Schwachstelle, die auftritt, wenn eine Funktion oder Operation einen inkorrekten Rückgabewert oder Statuscode zurückgibt, der das tatsächliche Ausführungsergebnis nicht widerspiegelt. Dies führt dazu, dass aufrufender Code sein Verhalten basierend auf fehlerhaften Informationen ändert. Bei sicherheitskritischen Entscheidungen können falsche Statuscodes dazu führen, dass das Produkt fälschlicherweise eine Aktion als sicher oder korrekt annimmt, obwohl sie es nicht ist, was zu Sicherheitsumgehungen und anderen Schwachstellen führt.
Risiko
Die Rückgabe falscher Statuscodes kann schwerwiegende Sicherheitsfolgen haben. Sicherheitsprüfungen, die Erfolg zurückgeben, wenn sie Fehler melden sollten, ermöglichen unbefugten Zugriff. API-Endpunkte, die HTTP 404 (Not Found) bei Authentifizierungsfehlern zurückgeben, geben Informationen über gültige Ressourcen preis. Zertifikatsvalidierung, die fälschlicherweise Erfolg zurückgibt, ermöglicht Man-in-the-Middle-Angriffe. Transaktionssysteme, die Erfolg bei fehlgeschlagenen Operationen melden, können Dateninkonsistenz und finanzielle Verluste verursachen. Der berühmte Apple "goto fail"-Bug (CVE-2014-1266) zeigte, wie fehlerhafter Kontrollfluss die SSL-Zertifikatsvalidierung vollständig umgehen kann.
Lösung
Stellen Sie sicher, dass Rückgabewerte und Statuscodes das Ergebnis von Operationen genau widerspiegeln. Verwenden Sie klare, eindeutige Rückgabewertkonventionen und dokumentieren Sie diese gründlich. Für Sicherheitsfunktionen sollte im Fehlerfall standardmäßig ein Fehlerstatus zurückgegeben werden (Fail-Closed-Prinzip). Testen Sie Fehlerpfade genauso gründlich wie Erfolgspfade. Verwenden Sie Code-Reviews und statische Analyse, um zu überprüfen, ob Rückgabewerte der beabsichtigten Semantik entsprechen. Erwägen Sie die Verwendung von Aufzählungstypen oder Ergebnisobjekten anstelle von rohen Ganzzahlen, um Rückgabewerte selbstdokumentierend zu machen. Setzen Sie Fuzzing ein, um Codepfade zu entdecken, die falsche Statuscodes zurückgeben.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Unerwarteter Zustand, veränderte Ausführungslogik -- Die Schwachstelle könnte das System in einen Zustand versetzen, der zu unerwarteter Logikausführung oder unbeabsichtigtem Verhalten führt. |
Beispielcode und Lösung
Verwundbarer Code
// VERWUNDBAR: Gibt falschen HTTP-Statuscode zurück
@WebServlet("/api/user")
public class VulnerableApiServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String userId = request.getParameter("id");
try {
User user = userService.findById(userId);
if (user == null) {
// VERWUNDBAR: 404 verrät, dass der Benutzer nicht existiert
response.setStatus(404); // Informationsleck!
return;
}
sendUserResponse(response, user);
} catch (IOException e) {
// VERWUNDBAR: Falscher Status für I/O-Fehler
response.setStatus(404); // Sollte 500 sein!
}
}
}
// VERWUNDBAR: Sicherheitsprüfung gibt falschen Wert zurück
public class VulnerableAuthChecker {
public boolean isAuthorized(User user, Resource resource) {
try {
return checkPermissions(user, resource);
} catch (Exception e) {
// VERWUNDBAR: Gibt true (autorisiert) bei Fehler zurück!
logger.error("Permission check failed", e);
return true; // Sollte false zurückgeben!
}
}
}
// VERWUNDBAR: Falscher Rückgabecode bei Validierungsfehler
int vulnerable_validate_certificate(X509 *cert) {
int result;
result = check_certificate_chain(cert);
if (result != 0) {
goto fail; // Erstes goto funktioniert
}
result = check_signature(cert);
if (result != 0) {
goto fail; // Zweites goto funktioniert
}
// VERWUNDBAR: Doppeltes goto (Apple "goto fail"-Stil Bug)
goto fail; // Springt bedingungslos zu fail!
result = check_expiration(cert);
if (result != 0) {
goto fail; // Wird nie erreicht!
}
return 0; // Erfolg - wird nie erreicht!
fail:
return 0; // VERWUNDBAR: Gibt Erfolg (0) auch bei Fehler zurück!
}
// VERWUNDBAR: Gibt falschen Wert für nicht existierenden Datensatz zurück
int vulnerable_lookup_record(const char *name) {
Record *rec = find_record(name);
if (rec == NULL) {
// VERWUNDBAR: Falscher Antwortcode
return RECORD_FOUND; // Sollte RECORD_NOT_FOUND sein!
}
return process_record(rec);
}
# VERWUNDBAR: Falscher Rückgabewert bei Authentifizierungsfehler
def vulnerable_authenticate(username, password):
user = database.find_user(username)
if user is None:
# VERWUNDBAR: Gibt 1 zurück (bedeutet oft Erfolg)
return 1 # Sollte Fehler anzeigen!
if not verify_password(password, user.password_hash):
# VERWUNDBAR: Gibt ebenfalls 1 zurück
return 1 # Sollte Fehler anzeigen!
return 0 # Erfolg
# VERWUNDBAR: Inkonsistente Fehlercodes
def vulnerable_file_operation(path):
if not os.path.exists(path):
return -1 # Datei nicht gefunden
try:
with open(path, 'r') as f:
return f.read()
except PermissionError:
return -1 # VERWUNDBAR: Gleicher Code, anderer Fehler!
except IOError:
return None # VERWUNDBAR: Inkonsistenter Rückgabetyp!
Sichere Lösung
// SICHER: Gibt angemessene Statuscodes zurück
@WebServlet("/api/user")
public class SecureApiServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String userId = request.getParameter("id");
try {
User user = userService.findById(userId);
if (user == null) {
// SICHER: Generischer Fehler, verrät nicht die Existenz des Benutzers
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
sendUserResponse(response, user);
} catch (IOException e) {
// SICHER: Korrekter Status für Serverfehler
logger.error("Error fetching user", e);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
// SICHER: Fail-Closed bei Fehler
public class SecureAuthChecker {
public boolean isAuthorized(User user, Resource resource) {
try {
return checkPermissions(user, resource);
} catch (Exception e) {
// SICHER: Gibt false (verweigert) bei Fehler zurück
logger.error("Permission check failed - denying access", e);
return false; // Fail-Closed!
}
}
}
// SICHER: Korrekte Zertifikatsvalidierung
typedef enum {
CERT_OK = 0,
CERT_CHAIN_INVALID = -1,
CERT_SIGNATURE_INVALID = -2,
CERT_EXPIRED = -3
} CertStatus;
CertStatus secure_validate_certificate(X509 *cert) {
CertStatus status;
status = check_certificate_chain(cert);
if (status != CERT_OK) {
return status; // Gibt spezifischen Fehler zurück
}
status = check_signature(cert);
if (status != CERT_OK) {
return status; // Gibt spezifischen Fehler zurück
}
// SICHER: Kein doppelter Sprung, Code fließt korrekt
status = check_expiration(cert);
if (status != CERT_OK) {
return status; // Gibt spezifischen Fehler zurück
}
return CERT_OK; // Gibt nur Erfolg zurück, wenn alle Prüfungen bestanden
}
// SICHER: Korrekte Fehlercodes
typedef enum {
RECORD_FOUND = 0,
RECORD_NOT_FOUND = -1,
RECORD_ERROR = -2
} RecordStatus;
RecordStatus secure_lookup_record(const char *name, Record **out_rec) {
Record *rec = find_record(name);
if (rec == NULL) {
// SICHER: Gibt korrekten Status zurück
*out_rec = NULL;
return RECORD_NOT_FOUND;
}
*out_rec = rec;
return RECORD_FOUND;
}
# SICHER: Verwendet Ergebnisobjekte für klare Statusmeldung
from enum import Enum
from dataclasses import dataclass
from typing import Optional
class AuthStatus(Enum):
SUCCESS = "success"
USER_NOT_FOUND = "user_not_found"
INVALID_PASSWORD = "invalid_password"
ACCOUNT_LOCKED = "account_locked"
@dataclass
class AuthResult:
status: AuthStatus
user: Optional['User'] = None
def secure_authenticate(username, password) -> AuthResult:
user = database.find_user(username)
if user is None:
# SICHER: Klarer Status für Benutzer nicht gefunden
return AuthResult(AuthStatus.USER_NOT_FOUND)
if user.is_locked:
return AuthResult(AuthStatus.ACCOUNT_LOCKED)
if not verify_password(password, user.password_hash):
# SICHER: Klarer Status für ungültiges Passwort
return AuthResult(AuthStatus.INVALID_PASSWORD)
return AuthResult(AuthStatus.SUCCESS, user=user)
# SICHER: Konsistente Fehlerbehandlung mit spezifischen Exceptions
class FileNotFoundError(Exception): pass
class PermissionDeniedError(Exception): pass
class FileReadError(Exception): pass
def secure_file_operation(path):
if not os.path.exists(path):
raise FileNotFoundError(f"File not found: {path}")
try:
with open(path, 'r') as f:
return f.read()
except PermissionError as e:
raise PermissionDeniedError(f"Cannot read {path}") from e
except IOError as e:
raise FileReadError(f"Error reading {path}") from e
CVE-Beispiele
- CVE-2003-1132 -- DNS-Server gibt falschen Antwortcode für nicht existierende AAAA-Einträge zurück.
- CVE-2001-1509 -- Hardware-spezifischer Systemaufruf gibt falsche geteuid-Ergebnisse zurück.
- CVE-2014-1266 -- Apple SSL "goto fail"-Bug - fehlerhafter Kontrollfluss umgeht die Zertifikatsvalidierung.
Referenzen
- MITRE Corporation. "CWE-393: Return of Wrong Status Code." https://cwe.mitre.org/data/definitions/393.html