Fehlende Meldung von Fehlerzustanden

Beschreibung

Fehlende Meldung von Fehlerzustanden ist eine Schwachstelle, die auftritt, wenn Software auf einen Fehler stößt, aber keinen Statuscode oder Rückgabewert liefert, der das Auftreten eines Fehlers anzeigt. Im Unterschied zu CWE-390, wo Fehler erkannt, aber ignoriert werden, betrifft diese Schwachstelle Operationen, die ihren Fehlerstatus nicht an den Aufrufer kommunizieren. Der aufrufende Code hat keine Möglichkeit zu wissen, dass ein Fehler aufgetreten ist, und fährt fort, als wäre die Operation erfolgreich gewesen, was potenziell zu Sicherheitslücken oder Datenkorruption führen kann.

Risiko

Wenn Fehler nicht gemeldet werden, geraten Systeme in unerwartete Zustände ohne jeglichen Hinweis auf das Problem. Sicherheitskritische Operationen, die stillschweigend fehlschlagen, können den Anschein erwecken, korrekt ausgeführt worden zu sein, was zu falscher Sicherheit führt. Kryptografische Operationen, die ohne Meldung auf unsichere Methoden zurückfallen, können sensible Daten preisgeben. Authentifizierungs- und Validierungsfunktionen, die trotz Fehlern Erfolg zurückgeben, ermöglichen unbefugten Zugriff. Die Datenintegrität wird beeinträchtigt, wenn Schreiboperationen Erfolg melden, aber tatsächlich fehlgeschlagen sind. Das Fehlen von Fehlermeldungen macht Debugging und Incident Response äußerst schwierig.

Lösung

Entwerfen Sie Funktionen so, dass sie ihren Erfolgs- oder Fehlerstatus stets über Rückgabewerte, Exceptions oder Ausgabeparameter kommunizieren. Dokumentieren Sie alle möglichen Fehlerzustande und ihre entsprechenden Rückgabewerte. Für sicherheitskritische Funktionen sollte im Fehlerfall ein Fehlerstatus zurückgegeben werden, anstatt stillschweigend fortzufahren (Fail-Closed-Prinzip). Verwenden Sie in Webanwendungen geeignete HTTP-Statuscodes zur Anzeige von Fehlerzustanden. Implementieren Sie eine ordnungsgemäße Protokollierung für alle Fehlerzustande, auch wenn ein Fehlerstatus zurückgegeben wird. Erwägen Sie die Verwendung von Exceptions in Sprachen, die diese unterstützen, um die Fehlerbehandlung explizit und schwerer ignorierbar zu machen.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Wenn Fehler nicht gemeldet werden, können Systeme in unerwartete Zustände geraten, die zu unbeabsichtigtem Verhalten führen.
SonstigesBereich: Sonstiges

Aufrufer können keine fundierten Entscheidungen treffen, wenn Fehlerzustande nicht kommuniziert werden.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Gibt 200 OK trotz Fehler zurück
@WebServlet("/process")
public class VulnerableServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
                          throws ServletException, IOException {
        try {
            processPayment(request);
        } catch (PaymentException e) {
            // VERWUNDBAR: Protokolliert den Fehler, gibt aber Erfolg zurück
            logger.error("Payment failed: " + e.toString());
            return;  // Gibt HTTP 200 OK zurück!
        }

        response.getWriter().write("Success");
    }
}

// VERWUNDBAR: Gibt Erfolg trotz Validierungsfehler zurück
public class VulnerableValidator {

    public boolean validateInput(String input) {
        try {
            performValidation(input);
            return true;
        } catch (ValidationException e) {
            // VERWUNDBAR: Gibt true trotz Fehler zurück
            logger.warn("Validation issue: " + e);
            return true;  // Sollte false zurückgeben!
        }
    }
}
// VERWUNDBAR: Keine Möglichkeit, dem Aufrufer einen Fehler zu melden
void vulnerable_crypto_init() {
    if (hardware_crypto_available()) {
        use_hardware_crypto();
    } else {
        // VERWUNDBAR: Fällt stillschweigend auf schwächere Implementierung zurück
        use_software_crypto();
        // Aufrufer weiß nicht, dass schwächere Kryptografie verwendet wird!
    }
}

// VERWUNDBAR: Gibt Erfolgscode trotz Fehler zurück
int vulnerable_pin_validate(const char *pin) {
    if (strlen(pin) != 4) {
        log_error("Invalid PIN length");
        return 0;  // VERWUNDBAR: 0 bedeutet OK in dieser API
    }

    if (!is_numeric(pin)) {
        log_error("PIN must be numeric");
        return 0;  // VERWUNDBAR: Gibt Erfolg zurück!
    }

    return verify_pin(pin);
}
# VERWUNDBAR: Stiller Rückfall auf unsichere Methode
def vulnerable_random_bytes(length):
    try:
        return os.urandom(length)
    except NotImplementedError:
        # VERWUNDBAR: Fällt ohne Meldung auf unsichere Zufallsgenerierung zurück
        import random
        return bytes([random.randint(0, 255) for _ in range(length)])

# VERWUNDBAR: Gibt None zurück statt eine Exception auszulösen
def vulnerable_authenticate(username, password):
    user = database.find_user(username)

    if user is None:
        # VERWUNDBAR: Gibt None zurück, gleich wie im Erfolgsfall unten
        return None

    if verify_password(password, user.password_hash):
        return user

    # VERWUNDBAR: Gibt bei Authentifizierungsfehler ebenfalls None zurück
    return None

Sichere Lösung

// SICHER: Gibt angemessene Statuscodes zurück
@WebServlet("/process")
public class SecureServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
                          throws ServletException, IOException {
        try {
            processPayment(request);
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().write("{\"status\": \"success\"}");
        } catch (PaymentException e) {
            // SICHER: Gibt Fehlerstatus-Code zurück
            logger.error("Payment failed: " + e.toString());
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.getWriter().write("{\"status\": \"error\", \"message\": \"Payment processing failed\"}");
        }
    }
}

// SICHER: Gibt korrektes Validierungsergebnis zurück
public class SecureValidator {

    public ValidationResult validateInput(String input) {
        try {
            performValidation(input);
            return ValidationResult.success();
        } catch (ValidationException e) {
            // SICHER: Meldet den Fehler
            logger.warn("Validation failed: " + e);
            return ValidationResult.failure(e.getMessage());
        }
    }
}
// SICHER: Gibt Status zurück, der anzeigt, welche Kryptografie verwendet wird
typedef enum {
    CRYPTO_HARDWARE,
    CRYPTO_SOFTWARE,
    CRYPTO_FAILED
} CryptoStatus;

CryptoStatus secure_crypto_init() {
    if (hardware_crypto_available()) {
        if (use_hardware_crypto() == 0) {
            return CRYPTO_HARDWARE;
        }
    }

    // SICHER: Meldet den Rückfall auf Software-Kryptografie
    if (use_software_crypto() == 0) {
        log_warning("Using software cryptography");
        return CRYPTO_SOFTWARE;
    }

    log_error("Crypto initialization failed");
    return CRYPTO_FAILED;
}

// SICHER: Korrekte Fehlercodes
typedef enum {
    PIN_OK = 0,
    PIN_INVALID_LENGTH = -1,
    PIN_NOT_NUMERIC = -2,
    PIN_VERIFY_FAILED = -3
} PinStatus;

PinStatus secure_pin_validate(const char *pin) {
    if (strlen(pin) != 4) {
        log_error("Invalid PIN length");
        return PIN_INVALID_LENGTH;  // SICHER: Fehlercode
    }

    if (!is_numeric(pin)) {
        log_error("PIN must be numeric");
        return PIN_NOT_NUMERIC;  // SICHER: Fehlercode
    }

    if (verify_pin(pin) != 0) {
        return PIN_VERIFY_FAILED;  // SICHER: Fehlercode
    }

    return PIN_OK;
}
# SICHER: Löst bei Rückfall eine Exception aus oder meldet den Status im Rückgabewert
def secure_random_bytes(length):
    try:
        return os.urandom(length)
    except NotImplementedError:
        # SICHER: Löst Exception bei sicherheitskritischem Fehler aus
        raise CryptoError("Secure random not available")

# Alternative: Gibt Status zusammen mit dem Ergebnis zurück
def secure_random_bytes_with_status(length):
    try:
        return (os.urandom(length), "hardware")
    except NotImplementedError:
        import secrets
        return (secrets.token_bytes(length), "software")

# SICHER: Unterscheidet Fehlerzustande im Rückgabewert
def secure_authenticate(username, password):
    user = database.find_user(username)

    if user is None:
        # SICHER: Löst spezifische Exception aus
        raise UserNotFoundError(f"User not found: {username}")

    if verify_password(password, user.password_hash):
        return user

    # SICHER: Löst Exception bei Authentifizierungsfehler aus
    raise AuthenticationError("Invalid password")

# Alternative: Gibt Ergebnisobjekt mit Status zurück
class AuthResult:
    def __init__(self, success, user=None, error=None):
        self.success = success
        self.user = user
        self.error = error

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

    if user is None:
        return AuthResult(False, error="User not found")

    if verify_password(password, user.password_hash):
        return AuthResult(True, user=user)

    return AuthResult(False, error="Invalid password")

CVE-Beispiele

  • CVE-2004-0063 -- Kryptografie-Bibliothek fällt ohne Fehlermeldung auf unsichere Zufallszahlengenerierung zurück.
  • CVE-2002-1446 -- Funktion gibt "OK" trotz ungültiger PIN-Validierung zurück.
  • CVE-2002-0499 -- PKCS#11-Bibliothek gibt bei ungültiger Signaturerkennung Erfolg zurück.
  • CVE-2005-2459 -- Kernel kürzt Pfadnamen stillschweigend und operiert auf dem falschen Verzeichnis.

Referenzen

  1. MITRE Corporation. "CWE-392: Missing Report of Error Condition." https://cwe.mitre.org/data/definitions/392.html