Nicht sicheres Scheitern (Fail Open)

Beschreibung

Nicht sicheres Scheitern tritt auf, wenn eine Anwendung auf Fehler oder Exceptions reagiert, indem sie standardmäßig in einen unsicheren Zustand wechselt, anstatt in einen sicheren. Wenn Sicherheitsprüfungen fehlschlagen, sollte die Anwendung den Zugriff verweigern (Fail-Closed), stattdessen erlaubt sie den Zugriff (Fail-Open). Dieses Anti-Pattern tritt bei Authentifizierung, Autorisierung, Eingabevalidierung und anderen sicherheitskritischen Codepfaden auf.

Risiko

Authentifizierungssysteme, die Zugriff gewähren, wenn Anmeldedaten nicht überprüft werden können. Autorisierungsprüfungen, die Aktionen erlauben, wenn die Richtlinienabfrage fehlschlägt. Firewall-Regeln, die Verkehr durchlassen, wenn bei der Regelverarbeitung Fehler auftreten. Eingabevalidierung, die Daten akzeptiert, wenn Validatoren abstürzen. Jede Sicherheitskontrolle, die im Fehlerfall standardmäßig "erlauben" anwendet, wird ausnutzbar, indem Fehler provoziert werden.

Lösung

Entwerfen Sie Systeme so, dass sie sicher scheitern (Fail-Closed) -- verweigern Sie den Zugriff bei jedem Fehler. Implementieren Sie explizite Standardmäßig-Verweigern-Richtlinien. Fangen Sie Exceptions auf geeigneter Ebene ab und wechseln Sie standardmäßig in den sicheren Zustand. Testen Sie Fehlerbehandlungspfade auf Sicherheitsauswirkungen. Protokollieren Sie Fehler für Incident Response. Verwenden Sie niemals Exception-Handling, um Sicherheitsprüfungen zu umgehen.

Häufige Auswirkungen

AuswirkungDetails
AuthentifizierungBereich: Umgehung

Fehler in Authentifizierungsprüfungen gewähren unbefugten Zugriff.
AutorisierungBereich: Eskalation

Berechtigungsfehler erlauben verbotene Aktionen.
ZugriffskontrolleBereich: Umgehung

Sicherheitskontrollen werden durch Fehler umgangen.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Authentifizierung scheitert offen
public class VulnerableAuthenticator {

    public boolean authenticate(String username, String password) {
        try {
            User user = userService.findByUsername(username);
            return passwordEncoder.matches(password, user.getPasswordHash());
        } catch (Exception e) {
            // SCHEITERT OFFEN: Gibt true bei Fehler zurück!
            logger.error("Auth error", e);
            return true;  // Gefährlich!
        }
    }

    // VERWUNDBAR: Autorisierung scheitert offen
    public boolean isAuthorized(User user, String resource, String action) {
        try {
            Policy policy = policyService.getPolicy(resource);
            return policy.allows(user, action);
        } catch (PolicyNotFoundException e) {
            // Keine Richtlinie gefunden = erlauben?
            return true;  // Sollte verweigern!
        } catch (Exception e) {
            // Fehler = erlauben?
            return true;  // Sollte verweigern!
        }
    }

    // VERWUNDBAR: Eingabevalidierung scheitert offen
    public void processInput(String input) {
        boolean isValid = true;  // Standard ist gültig!

        try {
            validator.validate(input);
        } catch (ValidationException e) {
            isValid = false;
        } catch (Exception e) {
            // Andere Fehler - trotzdem fortfahren
            logger.warn("Validation error", e);
            // isValid ist immer noch true!
        }

        if (isValid) {
            processData(input);
        }
    }
}
# VERWUNDBAR: Passwortprüfung scheitert offen
def check_password_vulnerable(username, password):
    try:
        user = get_user(username)
        stored_hash = user.password_hash
        return bcrypt.checkpw(password.encode(), stored_hash)
    except Exception as e:
        # Fehler = Erfolg? NEIN!
        logging.error(f"Password check error: {e}")
        return True  # Scheitert offen!

# VERWUNDBAR: Rate-Limiting scheitert offen
class VulnerableRateLimiter:
    def __init__(self, redis_client):
        self.redis = redis_client

    def is_allowed(self, ip_address):
        try:
            count = self.redis.incr(f"ratelimit:{ip_address}")
            self.redis.expire(f"ratelimit:{ip_address}", 60)
            return count <= 100
        except redis.RedisError:
            # Redis nicht erreichbar = kein Rate-Limiting!
            return True  # Scheitert offen!

# VERWUNDBAR: Berechtigungsprüfung scheitert offen
def can_access_resource_vulnerable(user_id, resource_id):
    try:
        permissions = fetch_permissions(user_id)
        return resource_id in permissions.allowed_resources
    except PermissionServiceError:
        # Dienstfehler = Zugriff erlauben?
        return True  # Falsch!
    except Exception:
        # Unbekannter Fehler = erlauben?
        return True  # Ebenfalls falsch!

# VERWUNDBAR: Zertifikatsvalidierung scheitert offen
def validate_certificate_vulnerable(cert):
    try:
        # Komplexe Validierungslogik
        verify_signature(cert)
        check_expiration(cert)
        check_revocation(cert)
        return True
    except Exception as e:
        # Jeder Fehler = gültiges Zertifikat?
        return True  # Katastrophal falsch!
// VERWUNDBAR: JWT-Überprüfung scheitert offen
async function verifyTokenVulnerable(token) {
    try {
        const decoded = jwt.verify(token, SECRET_KEY);
        return { valid: true, user: decoded };
    } catch (error) {
        if (error.name === 'TokenExpiredError') {
            return { valid: false, reason: 'expired' };
        }
        // Andere Fehler = gültiges Token?
        return { valid: true, user: {} };  // Scheitert offen!
    }
}

// VERWUNDBAR: Zugriffskontrolle scheitert offen
async function checkAccessVulnerable(userId, resourceId) {
    try {
        const acl = await loadACL(resourceId);
        return acl.hasAccess(userId);
    } catch (error) {
        console.error('ACL check failed:', error);
        return true;  // Scheitert offen!
    }
}

// VERWUNDBAR: Eingabebereinigung scheitert offen
function sanitizeInputVulnerable(input) {
    try {
        return DOMPurify.sanitize(input);
    } catch (error) {
        // Bereinigung fehlgeschlagen, Original zurückgeben?
        return input;  // XSS möglich!
    }
}

// VERWUNDBAR: Feature-Flag-Prüfung scheitert offen
function isFeatureEnabled(featureName, userId) {
    try {
        return featureService.check(featureName, userId);
    } catch (error) {
        // Dienstfehler = Feature aktiviert?
        return true;  // Könnte Beta-/interne Features preisgeben!
    }
}
<?php
// VERWUNDBAR: Login scheitert offen
function loginVulnerable($username, $password) {
    try {
        $user = findUser($username);
        if (password_verify($password, $user['password_hash'])) {
            return ['success' => true, 'user' => $user];
        }
        return ['success' => false, 'error' => 'Invalid credentials'];
    } catch (Exception $e) {
        // Datenbankfehler = Login erfolgreich?
        error_log($e->getMessage());
        return ['success' => true, 'user' => null];  // Falsch!
    }
}

// VERWUNDBAR: Admin-Prüfung scheitert offen
function isAdminVulnerable($userId) {
    try {
        $roles = fetchUserRoles($userId);
        return in_array('admin', $roles);
    } catch (Exception $e) {
        // Fehler beim Abrufen der Rollen = ist Admin?
        return true;  // Katastrophal!
    }
}

// VERWUNDBAR: CSRF-Validierung scheitert offen
function validateCsrfVulnerable($token) {
    try {
        return hash_equals($_SESSION['csrf_token'], $token);
    } catch (Exception $e) {
        // Keine Session oder Vergleichsfehler = gültig?
        return true;  // CSRF-Schutz umgangen!
    }
}
?>

Sichere Lösung

// SICHER: Authentifizierung scheitert geschlossen
public class SafeAuthenticator {

    public boolean authenticate(String username, String password) {
        try {
            User user = userService.findByUsername(username);
            if (user == null) {
                return false;  // Benutzer nicht gefunden = fehlgeschlagen
            }
            return passwordEncoder.matches(password, user.getPasswordHash());
        } catch (Exception e) {
            // SCHEITERT GESCHLOSSEN: Gibt false bei jedem Fehler zurück
            logger.error("Authentication error - denying access", e);
            return false;
        }
    }

    // SICHER: Autorisierung scheitert geschlossen
    public boolean isAuthorized(User user, String resource, String action) {
        // Standard ist verweigern
        boolean authorized = false;

        try {
            Policy policy = policyService.getPolicy(resource);
            if (policy != null) {
                authorized = policy.allows(user, action);
            }
            // Keine Richtlinie = authorized bleibt false (verweigern)
        } catch (Exception e) {
            // Jeder Fehler = verweigern
            logger.error("Authorization check failed - denying", e);
            authorized = false;
        }

        return authorized;
    }

    // SICHER: Eingabevalidierung scheitert geschlossen
    public void processInput(String input) {
        boolean isValid = false;  // Standard ist ungültig!

        try {
            isValid = validator.validate(input);
        } catch (Exception e) {
            // Jeder Fehler = ungültig
            logger.warn("Validation error - rejecting input", e);
            isValid = false;
        }

        if (isValid) {
            processData(input);
        } else {
            throw new InvalidInputException("Input validation failed");
        }
    }
}
# SICHER: Passwortprüfung scheitert geschlossen
def check_password_safe(username: str, password: str) -> bool:
    try:
        user = get_user(username)
        if user is None:
            return False  # Kein Benutzer = fehlgeschlagen

        stored_hash = user.password_hash
        return bcrypt.checkpw(password.encode(), stored_hash)
    except Exception as e:
        # Jeder Fehler = Authentifizierung fehlgeschlagen
        logging.error(f"Password check error - denying access: {e}")
        return False  # Scheitert geschlossen!

# SICHER: Rate-Limiting scheitert geschlossen
class SafeRateLimiter:
    def __init__(self, redis_client):
        self.redis = redis_client

    def is_allowed(self, ip_address: str) -> bool:
        try:
            count = self.redis.incr(f"ratelimit:{ip_address}")
            self.redis.expire(f"ratelimit:{ip_address}", 60)
            return count <= 100
        except redis.RedisError as e:
            # Redis nicht erreichbar = Anfragen verweigern (Fail-Closed)
            logging.error(f"Rate limiter error - denying: {e}")
            return False

# SICHER: Berechtigungsprüfung scheitert geschlossen
def can_access_resource_safe(user_id: str, resource_id: str) -> bool:
    # Explizites Standardmäßig-Verweigern
    allowed = False

    try:
        permissions = fetch_permissions(user_id)
        if permissions and resource_id in permissions.allowed_resources:
            allowed = True
    except Exception as e:
        # Jeder Fehler = verweigern
        logging.error(f"Permission check error - denying: {e}")
        allowed = False

    return allowed

# SICHER: Zertifikatsvalidierung scheitert geschlossen
def validate_certificate_safe(cert) -> bool:
    try:
        verify_signature(cert)
        check_expiration(cert)
        check_revocation(cert)
        return True
    except Exception as e:
        # Jeder Validierungsfehler = ungültiges Zertifikat
        logging.error(f"Certificate validation failed: {e}")
        return False
// SICHER: JWT-Überprüfung scheitert geschlossen
async function verifyTokenSafe(token) {
    try {
        const decoded = jwt.verify(token, SECRET_KEY);
        return { valid: true, user: decoded };
    } catch (error) {
        // ALLE Fehler resultieren in ungültigem Token
        console.error('Token verification failed:', error.message);
        return { valid: false, user: null, reason: error.name };
    }
}

// SICHER: Zugriffskontrolle scheitert geschlossen
async function checkAccessSafe(userId, resourceId) {
    // Standardmäßig verweigern
    let hasAccess = false;

    try {
        const acl = await loadACL(resourceId);
        if (acl) {
            hasAccess = acl.hasAccess(userId);
        }
    } catch (error) {
        console.error('ACL check failed - denying access:', error);
        hasAccess = false;  // Explizites Verweigern bei Fehler
    }

    return hasAccess;
}

// SICHER: Eingabebereinigung scheitert geschlossen
function sanitizeInputSafe(input) {
    try {
        const sanitized = DOMPurify.sanitize(input);
        return { success: true, data: sanitized };
    } catch (error) {
        // Bereinigung fehlgeschlagen = Eingabe ablehnen
        console.error('Sanitization failed:', error);
        return { success: false, data: null };
    }
}

// SICHER: Feature-Flag scheitert geschlossen
function isFeatureEnabled(featureName, userId) {
    // Standard: Features sind deaktiviert
    let enabled = false;

    try {
        enabled = featureService.check(featureName, userId);
    } catch (error) {
        console.error('Feature check failed - defaulting to disabled');
        enabled = false;
    }

    return enabled;
}
<?php
// SICHER: Login scheitert geschlossen
function loginSafe($username, $password) {
    try {
        $user = findUser($username);
        if (!$user) {
            return ['success' => false, 'error' => 'Invalid credentials'];
        }

        if (password_verify($password, $user['password_hash'])) {
            return ['success' => true, 'user' => $user];
        }

        return ['success' => false, 'error' => 'Invalid credentials'];
    } catch (Exception $e) {
        // Jeder Fehler = Login fehlgeschlagen
        error_log("Login error - denying access: " . $e->getMessage());
        return ['success' => false, 'error' => 'Authentication error'];
    }
}

// SICHER: Admin-Prüfung scheitert geschlossen
function isAdminSafe($userId) {
    // Standard: kein Admin
    $isAdmin = false;

    try {
        $roles = fetchUserRoles($userId);
        if (is_array($roles) && in_array('admin', $roles)) {
            $isAdmin = true;
        }
    } catch (Exception $e) {
        error_log("Admin check error - denying: " . $e->getMessage());
        $isAdmin = false;
    }

    return $isAdmin;
}

// SICHER: CSRF-Validierung scheitert geschlossen
function validateCsrfSafe($token) {
    try {
        if (!isset($_SESSION['csrf_token']) || empty($token)) {
            return false;
        }
        return hash_equals($_SESSION['csrf_token'], $token);
    } catch (Exception $e) {
        // Jeder Fehler = ungültiges CSRF-Token
        error_log("CSRF validation error: " . $e->getMessage());
        return false;
    }
}
?>

Ausgenutzt in der Praxis

Authentifizierungsumgehung

Systeme, die Zugriff gewährten, wenn Authentifizierungsdienste nicht erreichbar waren.

Firewall-Regeln

Netzwerksicherheit, die bei hoher Last offen scheiterte.

API-Rate-Limiting

Rate-Limiter, die durch Provozieren von Fehlern deaktiviert wurden.


Tools zum Testen und Ausnutzen

  • Chaos-Engineering-Tools zum Auslösen von Fehlern.

  • Fehlerinjektionstests.

  • Simulation von Abhängigkeitsausfällen.


CVE-Beispiele

  • CVEs durch offen scheiternde Sicherheitskontrollen.

  • Authentifizierungsumgehung durch Fehlerauslösung.


Referenzen

  1. MITRE. "CWE-636: Not Failing Securely (Failing Open)." https://cwe.mitre.org/data/definitions/636.html

  2. OWASP. "Fail Securely"-Designprinzip.