Skip to content

CWE-693: Protection Mechanism Failure

Overview

Protection mechanism failure occurs when security controls are improperly implemented, misconfigured, or bypassable, relying on single layers of defense, client-side checks, or weak validation, enabling attackers to circumvent intended security protections.

OWASP Classification

A06:2025 - Insecure Design

Risk

High: Failed protection mechanisms enable complete security bypass (client-side validation), authentication defeat (weak implementations), authorization bypass, input validation circumvention, and exploitation despite presence of security controls.

Remediation Strategy

Implement Defense in Depth (Primary Defense)

# VULNERABLE - single layer

@app.route('/admin')
def admin_panel():
    # Only checks cookie
    if request.cookies.get('admin') == 'true':
        return render_admin()  # Client can set cookie!

# SECURE - multiple layers

@app.route('/admin')
@require_authentication  # Layer 1: Must be logged in
@require_mfa  # Layer 2: Multi-factor auth
@require_role('ADMIN')  # Layer 3: Role check
@rate_limit(10, per_minute=1)  # Layer 4: Rate limiting
def admin_panel():
    # Log access for audit
    log_admin_access(current_user)
    return render_admin()

Never Trust Client-Side Validation

// VULNERABLE - client-side only
function submitForm() {
    const price = document.getElementById('price').value;
    if (price > 0 && price < 1000) {  // Client-side check
        // Attacker bypasses with browser dev tools
        document.getElementById('form').submit();
    }
}

// SECURE - server-side validation
@app.route('/submit', methods=['POST'])
def submit():
    price = float(request.form['price'])

    # Server-side validation (required)
    if price <= 0 or price >= 1000:
        abort(400, "Invalid price")

    # Additional checks
    if price != get_expected_price(request.form['item_id']):
        abort(400, "Price mismatch")

    process_order(price)

Proper Error Handling

// VULNERABLE - exception bypasses check
public void accessResource(String userId) {
    try {
        checkPermission(userId);  // May throw exception
    } catch (Exception e) {
        // Swallowing exception
    }
    // Continues even if permission check failed!
    grantAccess();
}

// SECURE - fail securely
public void accessResource(String userId) 
        throws AccessDeniedException {
    try {
        checkPermission(userId);
    } catch (PermissionException e) {
        log.error("Permission check failed", e);
        throw new AccessDeniedException();
    }
    grantAccess();
}

Validate All Inputs

# VULNERABLE - incomplete validation

def process_upload(filename):
    # Only checks extension
    if filename.endswith('.jpg'):
        save_file(filename)  # Bypass: shell.php.jpg

# SECURE - comprehensive validation

import re
from pathlib import Path

ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
MAX_SIZE = 5 * 1024 * 1024  # 5MB

def process_upload(file):
    # Check file size
    if file.size > MAX_SIZE:
        raise ValueError("File too large")

    # Check extension (multiple checks)
    filename = secure_filename(file.filename)
    ext = Path(filename).suffix[1:].lower()
    if ext not in ALLOWED_EXTENSIONS:
        raise ValueError("Invalid file type")

    # Check MIME type
    if file.content_type not in ['image/jpeg', 'image/png', 'image/gif']:
        raise ValueError("Invalid content type")

    # Check magic bytes
    header = file.read(16)
    file.seek(0)
    if not is_valid_image_header(header):
        raise ValueError("Invalid image file")

    # Sanitize filename
    safe_name = re.sub(r'[^a-zA-Z0-9._-]', '', filename)

    save_file(safe_name, file)

Remediation Steps

Core principle: All security-sensitive functionality must be protected by explicit, enforced security controls that cannot be bypassed by request manipulation or execution path changes; security mechanisms must exist and be correctly enforced.

  1. Identify the failed protection mechanism
  2. Trace bypass paths
  3. Locate missing layers
  4. Apply defense-in-depth: Add multiple independent security layers (authentication + authorization + input validation + rate limiting)
  5. Implement server-side enforcement: Move all security checks to server-side code where they cannot be bypassed
  6. Test bypass scenarios: Attempt to circumvent each protection mechanism (disable JavaScript, modify requests, force exceptions)
  7. Document the defense layers and verify all critical paths have multiple protections

Common Protection Failures

Client-Side Only:

<!-- Bypassable -->
<form onsubmit="return validateForm()">
    <input name="price" id="price">
</form>
<script>
function validateForm() {
    return document.getElementById('price').value < 100;
}
</script>
<!-- Attacker: Disable JavaScript or modify DOM -->

Weak Authentication:

# Easily bypassed

if request.headers.get('X-Auth') == 'secret123':
    allow_access()

Insufficient Authorization:

// Checks if admin, but not if admin for THIS resource
if (user.isAdmin()) {
    deleteUser(targetUserId);  // Can delete any user!
}

Incomplete Input Validation:

# Only checks for single quote

if "'" not in user_input:
    query = f"SELECT * FROM users WHERE id = {user_input}"

# Bypass: 1 OR 1=1--

Defense in Depth Example

class SecureAPI:
    def __init__(self):
        self.rate_limiter = RateLimiter()
        self.validator = InputValidator()
        self.auth = AuthenticationService()
        self.authz = AuthorizationService()

    @require_https  # Layer 1: Encrypted transport
    def handle_request(self, request):
        # Layer 2: Rate limiting
        if not self.rate_limiter.allow(request.ip):
            abort(429)

        # Layer 3: Authentication
        user = self.auth.authenticate(request.token)
        if not user:
            abort(401)

        # Layer 4: Authorization
        if not self.authz.can_access(user, request.resource):
            abort(403)

        # Layer 5: Input validation
        data = self.validator.validate(request.data)

        # Layer 6: Business logic with additional checks
        result = self.process(user, data)

        # Layer 7: Output encoding
        return self.encode_output(result)

Dynamic Scan Guidance

For guidance on remediating this CWE when detected by dynamic (DAST) scanners:

Additional Resources