Weakness reference
CWE-421

Race Condition During Access to Alternate Channel

This weakness occurs when software protects a resource through one access path but leaves an alternate path unprotected or inadequately secured. An attacker…

01Summary

This weakness occurs when software protects a resource through one access path but leaves an alternate path unprotected or inadequately secured. An attacker can exploit the timing gap between the two paths to bypass intended access controls. This is particularly dangerous because the primary security mechanism may appear to work correctly while the alternate channel silently undermines it.

02How It Happens

Most applications provide multiple ways to access the same resource or functionality—for example, a web interface and an API endpoint, a file accessed through a web server and directly from the filesystem, or a resource protected by one authentication method but accessible through another. When developers secure only the primary path and overlook the alternate channel, or when they secure both but with different timing or logic, an attacker can race between the two to create a window of vulnerability. The race condition emerges because the checks on each channel may not be atomic or synchronized, allowing an attacker to slip a request through the unprotected path while the primary path's defenses are in transition.

03Real-World Impact

Successful exploitation can lead to unauthorized access to sensitive data, privilege escalation, or modification of protected resources. For instance, an attacker might bypass authentication on an API endpoint while the web interface enforces login, or access a file through a direct path while the intended access control on the primary channel is being evaluated. The impact ranges from information disclosure to complete compromise of the resource, depending on what the alternate channel exposes.

04Vulnerable & Fixed Patterns

Vulnerable pattern
# Primary channel: web request with authentication check
def get_user_data(user_id, request):
    if not request.user.is_authenticated:
        raise PermissionDenied("Not authenticated")
    return fetch_user_from_db(user_id)

# Alternate channel: direct function call (e.g., internal API, scheduled task)
# No authentication check performed
def internal_fetch_user(user_id):
    return fetch_user_from_db(user_id)

# Attacker can call internal_fetch_user() directly if it's exposed
# or race between the two paths during a state transition

Why it's vulnerable:
The alternate channel (internal_fetch_user) performs the same operation without the authentication guard present in the primary channel. An attacker who discovers or can invoke the alternate path bypasses the intended access control entirely.

Fixed pattern
# Centralized access control applied to all channels
def _check_access(user, resource_id):
    if not user.is_authenticated:
        raise PermissionDenied("Not authenticated")
    if not user.can_access(resource_id):
        raise PermissionDenied("Access denied")

def get_user_data(user_id, request):
    _check_access(request.user, user_id)
    return fetch_user_from_db(user_id)

def internal_fetch_user(user_id, request_user):
    # Alternate channel also enforces the same check
    _check_access(request_user, user_id)
    return fetch_user_from_db(user_id)
Vulnerable pattern
// Primary channel: web form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_SESSION['user_id'])) {
        die('Not authenticated');
    }
    $file_id = $_POST['file_id'];
    delete_file($file_id);
}

// Alternate channel: direct file deletion via URL parameter
// No session check
if (isset($_GET['delete_file'])) {
    $file_id = $_GET['delete_file'];
    delete_file($file_id);
}

Why it's vulnerable:
The GET parameter path allows file deletion without verifying the user's session, while the POST path enforces authentication. An attacker can bypass the primary control by using the alternate channel.

Fixed pattern
// Centralized access control function
function require_auth_and_permission($action, $resource_id) {
    if (!isset($_SESSION['user_id'])) {
        die('Not authenticated');
    }
    if (!user_can_perform($action, $resource_id, $_SESSION['user_id'])) {
        die('Permission denied');
    }
}

// Primary channel
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $file_id = $_POST['file_id'];
    require_auth_and_permission('delete', $file_id);
    delete_file($file_id);
}

// Alternate channel: same check applied
if (isset($_GET['delete_file'])) {
    $file_id = $_GET['delete_file'];
    require_auth_and_permission('delete', $file_id);
    delete_file($file_id);
}

05Prevention Checklist

Identify all access paths
to each protected resource (web UI, API, internal functions, scheduled tasks, direct file access) and document them explicitly.
Apply the same access control logic
to every path that accesses the resource; use a centralized, reusable function or middleware rather than duplicating checks.
Use atomic operations
where possible; ensure that authentication and authorization checks cannot be bypassed by timing attacks between separate steps.
Test alternate channels
as part of security testing; verify that disabling or removing the primary channel's protection does not expose the resource through other paths.
Minimize the number of access paths
to sensitive resources; consolidate or remove unnecessary alternate channels if they are not essential.
Log and monitor access
through all channels; unusual patterns (e.g., direct function calls from unexpected sources) may indicate exploitation attempts.

06Signs You May Already Be Affected

Look for unexpected access to protected resources through paths that should not be accessible—for example, API calls succeeding without authentication tokens, internal functions being invoked from external contexts, or file modifications occurring without corresponding entries in the primary channel's audit logs. Unusual timing patterns in logs (e.g., rapid successive requests on different endpoints) may also suggest an attacker is racing between channels.