Not Failing Securely (Failing Open)
Description
Not Failing Securely occurs when an application responds to errors or exceptions by defaulting to an insecure state rather than a secure one. When security checks fail, the application should deny access (fail closed), but instead it permits access (fails open). This anti-pattern appears in authentication, authorization, input validation, and other security-critical code paths.
Risk
Authentication systems that grant access when unable to verify credentials. Authorization checks that permit actions when policy lookup fails. Firewall rules that allow traffic when rule processing errors occur. Input validation that accepts data when validators crash. Any security control that defaults to "allow" on failure becomes exploitable by triggering errors.
Solution
Design systems to fail closed - deny access on any error. Implement explicit deny-by-default policies. Catch exceptions at appropriate levels and default to secure state. Test error handling paths for security implications. Log failures for incident response. Never use exception handling to bypass security checks.
Common Consequences
| Impact | Details |
|---|---|
| Authentication | Scope: Bypass Errors in auth checks grant unauthorized access. |
| Authorization | Scope: Escalation Permission errors allow forbidden actions. |
| Access Control | Scope: Circumvention Security controls bypassed through errors. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Authentication fails open
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) {
// FAILS OPEN: Returns true on error!
logger.error("Auth error", e);
return true; // Dangerous!
}
}
// VULNERABLE: Authorization fails open
public boolean isAuthorized(User user, String resource, String action) {
try {
Policy policy = policyService.getPolicy(resource);
return policy.allows(user, action);
} catch (PolicyNotFoundException e) {
// No policy found = allow?
return true; // Should deny!
} catch (Exception e) {
// Error = allow?
return true; // Should deny!
}
}
// VULNERABLE: Input validation fails open
public void processInput(String input) {
boolean isValid = true; // Default to valid!
try {
validator.validate(input);
} catch (ValidationException e) {
isValid = false;
} catch (Exception e) {
// Other errors - continue anyway
logger.warn("Validation error", e);
// isValid still true!
}
if (isValid) {
processData(input);
}
}
}
# VULNERABLE: Password check fails open
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:
# Error = success? NO!
logging.error(f"Password check error: {e}")
return True # Fails open!
# VULNERABLE: Rate limiting fails open
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 down = no rate limiting!
return True # Fails open!
# VULNERABLE: Permission check fails open
def can_access_resource_vulnerable(user_id, resource_id):
try:
permissions = fetch_permissions(user_id)
return resource_id in permissions.allowed_resources
except PermissionServiceError:
# Service error = allow access?
return True # Wrong!
except Exception:
# Unknown error = allow?
return True # Also wrong!
# VULNERABLE: Certificate validation fails open
def validate_certificate_vulnerable(cert):
try:
# Complex validation logic
verify_signature(cert)
check_expiration(cert)
check_revocation(cert)
return True
except Exception as e:
# Any error = valid cert?
return True # Catastrophically wrong!
// VULNERABLE: JWT verification fails open
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' };
}
// Other errors = valid token?
return { valid: true, user: {} }; // Fails open!
}
}
// VULNERABLE: Access control fails open
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; // Fails open!
}
}
// VULNERABLE: Input sanitization fails open
function sanitizeInputVulnerable(input) {
try {
return DOMPurify.sanitize(input);
} catch (error) {
// Sanitization failed, return original?
return input; // XSS possible!
}
}
// VULNERABLE: Feature flag check fails open
function isFeatureEnabled(featureName, userId) {
try {
return featureService.check(featureName, userId);
} catch (error) {
// Service error = feature enabled?
return true; // Could expose beta/internal features!
}
}
<?php
// VULNERABLE: Login fails open
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) {
// Database error = login success?
error_log($e->getMessage());
return ['success' => true, 'user' => null]; // Wrong!
}
}
// VULNERABLE: Admin check fails open
function isAdminVulnerable($userId) {
try {
$roles = fetchUserRoles($userId);
return in_array('admin', $roles);
} catch (Exception $e) {
// Error fetching roles = is admin?
return true; // Catastrophic!
}
}
// VULNERABLE: CSRF validation fails open
function validateCsrfVulnerable($token) {
try {
return hash_equals($_SESSION['csrf_token'], $token);
} catch (Exception $e) {
// No session or comparison error = valid?
return true; // CSRF protection bypassed!
}
}
?>
Fixed Code
// SAFE: Authentication fails closed
public class SafeAuthenticator {
public boolean authenticate(String username, String password) {
try {
User user = userService.findByUsername(username);
if (user == null) {
return false; // User not found = fail
}
return passwordEncoder.matches(password, user.getPasswordHash());
} catch (Exception e) {
// FAILS CLOSED: Returns false on any error
logger.error("Authentication error - denying access", e);
return false;
}
}
// SAFE: Authorization fails closed
public boolean isAuthorized(User user, String resource, String action) {
// Default to deny
boolean authorized = false;
try {
Policy policy = policyService.getPolicy(resource);
if (policy != null) {
authorized = policy.allows(user, action);
}
// No policy = authorized stays false (deny)
} catch (Exception e) {
// Any error = deny
logger.error("Authorization check failed - denying", e);
authorized = false;
}
return authorized;
}
// SAFE: Input validation fails closed
public void processInput(String input) {
boolean isValid = false; // Default to invalid!
try {
isValid = validator.validate(input);
} catch (Exception e) {
// Any error = invalid
logger.warn("Validation error - rejecting input", e);
isValid = false;
}
if (isValid) {
processData(input);
} else {
throw new InvalidInputException("Input validation failed");
}
}
}
# SAFE: Password check fails closed
def check_password_safe(username: str, password: str) -> bool:
try:
user = get_user(username)
if user is None:
return False # No user = fail
stored_hash = user.password_hash
return bcrypt.checkpw(password.encode(), stored_hash)
except Exception as e:
# Any error = authentication failure
logging.error(f"Password check error - denying access: {e}")
return False # Fails closed!
# SAFE: Rate limiting fails closed
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 down = deny requests (fail closed)
logging.error(f"Rate limiter error - denying: {e}")
return False
# SAFE: Permission check fails closed
def can_access_resource_safe(user_id: str, resource_id: str) -> bool:
# Explicit default deny
allowed = False
try:
permissions = fetch_permissions(user_id)
if permissions and resource_id in permissions.allowed_resources:
allowed = True
except Exception as e:
# Any error = deny
logging.error(f"Permission check error - denying: {e}")
allowed = False
return allowed
# SAFE: Certificate validation fails closed
def validate_certificate_safe(cert) -> bool:
try:
verify_signature(cert)
check_expiration(cert)
check_revocation(cert)
return True
except Exception as e:
# Any validation error = invalid certificate
logging.error(f"Certificate validation failed: {e}")
return False
// SAFE: JWT verification fails closed
async function verifyTokenSafe(token) {
try {
const decoded = jwt.verify(token, SECRET_KEY);
return { valid: true, user: decoded };
} catch (error) {
// ALL errors result in invalid token
console.error('Token verification failed:', error.message);
return { valid: false, user: null, reason: error.name };
}
}
// SAFE: Access control fails closed
async function checkAccessSafe(userId, resourceId) {
// Default deny
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; // Explicit deny on error
}
return hasAccess;
}
// SAFE: Input sanitization fails closed
function sanitizeInputSafe(input) {
try {
const sanitized = DOMPurify.sanitize(input);
return { success: true, data: sanitized };
} catch (error) {
// Sanitization failed = reject input
console.error('Sanitization failed:', error);
return { success: false, data: null };
}
}
// SAFE: Feature flag fails closed
function isFeatureEnabled(featureName, userId) {
// Default: features are disabled
let enabled = false;
try {
enabled = featureService.check(featureName, userId);
} catch (error) {
console.error('Feature check failed - defaulting to disabled');
enabled = false;
}
return enabled;
}
<?php
// SAFE: Login fails closed
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) {
// Any error = login failure
error_log("Login error - denying access: " . $e->getMessage());
return ['success' => false, 'error' => 'Authentication error'];
}
}
// SAFE: Admin check fails closed
function isAdminSafe($userId) {
// Default: not 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;
}
// SAFE: CSRF validation fails closed
function validateCsrfSafe($token) {
try {
if (!isset($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
} catch (Exception $e) {
// Any error = invalid CSRF token
error_log("CSRF validation error: " . $e->getMessage());
return false;
}
}
?>
Exploited in the Wild
Authentication Bypass
Systems granting access when auth services were unreachable.
Firewall Rules
Network security failing open during high load.
API Rate Limiting
Rate limiters disabled by triggering errors.
Tools to test/exploit
-
Chaos engineering tools to induce failures.
-
Error injection testing.
-
Dependency failure simulation.
CVE Examples
-
CVEs from security controls failing open.
-
Authentication bypass through error induction.
References
-
MITRE. "CWE-636: Not Failing Securely (Failing Open)." https://cwe.mitre.org/data/definitions/636.html
-
OWASP. "Fail Securely" design principle.