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
| Auswirkung | Details |
|---|---|
| Authentifizierung | Bereich: Umgehung Fehler in Authentifizierungsprüfungen gewähren unbefugten Zugriff. |
| Autorisierung | Bereich: Eskalation Berechtigungsfehler erlauben verbotene Aktionen. |
| Zugriffskontrolle | Bereich: 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
-
MITRE. "CWE-636: Not Failing Securely (Failing Open)." https://cwe.mitre.org/data/definitions/636.html
-
OWASP. "Fail Securely"-Designprinzip.