Missing Critical Step in Authentication

Description

Missing Critical Step in Authentication is a vulnerability that occurs when a product implements an authentication technique but omits a critical step that weakens the overall technique. Authentication algorithms are designed with specific sequences of verification steps, each serving a purpose in ensuring the authenticity of users or systems. When implementations skip steps - whether due to oversight, optimization attempts, or incomplete understanding of the protocol - the authentication mechanism becomes vulnerable to bypass attacks. Common examples include skipping signature verification, omitting shared secret validation, or bypassing protocol initialization steps.

Risk

Skipping authentication steps creates exploitable gaps that attackers can leverage to bypass security controls entirely. Even seemingly minor omissions can have catastrophic consequences - as seen with the RADIUS authentication bypass where missing shared secret verification in response packets allowed complete authentication bypass through spoofed server replies. The risk is amplified because these vulnerabilities often appear in otherwise well-designed systems where the algorithm selection was appropriate. Attackers who understand the protocol can identify which step was skipped and craft attacks specifically targeting that gap. These vulnerabilities are particularly dangerous in network protocols where attackers can intercept and modify communications.

Solution

Implement authentication algorithms exactly as specified without shortcuts or optimizations that skip steps. Perform comprehensive code reviews against the algorithm specification to verify all steps are present. Use established authentication libraries that have been thoroughly vetted rather than custom implementations. Create unit tests that verify each step of the authentication process executes and cannot be bypassed. Document the expected authentication flow and validate the implementation matches it. Pay particular attention to error handling paths that might skip steps when errors occur. Consider formal verification for critical authentication components to mathematically prove correctness.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Missing authentication steps allow attackers to bypass protection mechanisms entirely, gaining unauthorized access to protected resources, user accounts, and system functionality.
Integrity, ConfidentialityScope: Integrity, Confidentiality

With authentication bypassed, attackers can assume the identity of legitimate users, execute unauthorized commands, read sensitive data, and modify protected information.

Example Code

Vulnerable Code (Python)

The following examples demonstrate authentication with missing critical steps:

# Vulnerable: RADIUS-style authentication missing shared secret verification
import hashlib
import socket
import struct

class VulnerableRADIUSClient:
    def __init__(self, server, secret):
        self.server = server
        self.secret = secret.encode()
        self.request_authenticator = None

    def authenticate(self, username, password):
        # Step 1: Create Access-Request packet
        request = self.create_access_request(username, password)
        self.request_authenticator = request[4:20]  # Store for verification

        # Step 2: Send request
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.sendto(request, (self.server, 1812))

        # Step 3: Receive response
        response, _ = sock.recvfrom(4096)

        # Vulnerable: Missing critical step!
        # Should verify Response-Authenticator = MD5(Code+ID+Length+RequestAuth+Attributes+Secret)
        # Instead, just checks the code field

        code = response[0]
        if code == 2:  # Access-Accept
            # Attacker can spoof Access-Accept without knowing secret!
            return True
        return False

    def verify_response_authenticator(self, response):
        # This method exists but is NEVER CALLED!
        code = response[0:1]
        identifier = response[1:2]
        length = response[2:4]
        response_auth = response[4:20]
        attributes = response[20:]

        # Correct calculation
        expected = hashlib.md5(
            code + identifier + length + self.request_authenticator +
            attributes + self.secret
        ).digest()

        return response_auth == expected
// Vulnerable: OAuth-style authentication missing state parameter verification
public class VulnerableOAuthClient {

    private String clientId;
    private String clientSecret;
    private String redirectUri;

    public String startAuthorization() {
        // Step 1: Generate state parameter for CSRF protection
        String state = generateRandomState();
        // Vulnerable: State stored but never verified!
        sessionStore.put("oauth_state", state);

        // Step 2: Redirect to authorization server
        String authUrl = String.format(
            "https://auth.example.com/authorize?client_id=%s&redirect_uri=%s&state=%s",
            clientId, redirectUri, state
        );

        return authUrl;
    }

    public TokenResponse handleCallback(HttpServletRequest request) {
        String code = request.getParameter("code");
        String returnedState = request.getParameter("state");

        // Vulnerable: Missing critical step - state verification!
        // String expectedState = sessionStore.get("oauth_state");
        // if (!expectedState.equals(returnedState)) {
        //     throw new SecurityException("CSRF detected");
        // }

        // Proceeds without verifying state, allowing CSRF attacks
        return exchangeCodeForToken(code);
    }

    private TokenResponse exchangeCodeForToken(String code) {
        // Exchange authorization code for access token
        // ...
    }
}
// Vulnerable: TLS-style handshake skipping certificate verification step
#include <openssl/ssl.h>

typedef struct {
    int step;
    SSL *ssl;
    X509 *peer_cert;
} HandshakeContext;

int vulnerable_handshake(HandshakeContext *ctx) {
    int result;

    // Step 1: Send ClientHello
    result = send_client_hello(ctx->ssl);
    if (result != 0) return -1;
    ctx->step = 1;

    // Step 2: Receive ServerHello
    result = receive_server_hello(ctx->ssl);
    if (result != 0) return -1;
    ctx->step = 2;

    // Step 3: Receive Certificate
    ctx->peer_cert = receive_certificate(ctx->ssl);
    if (ctx->peer_cert == NULL) return -1;
    ctx->step = 3;

    // Vulnerable: Step 4 MISSING - Certificate verification!
    // Should call: verify_certificate_chain(ctx->peer_cert)
    // Should call: verify_hostname(ctx->peer_cert, expected_host)

    // Step 5: Proceed directly to key exchange
    result = perform_key_exchange(ctx->ssl);
    if (result != 0) return -1;
    ctx->step = 5;

    // Attacker can present any certificate!
    return 0;
}

Fixed Code (Python)

# Fixed: RADIUS authentication with proper shared secret verification
import hashlib
import hmac
import socket
import struct

class SecureRADIUSClient:
    def __init__(self, server, secret):
        self.server = server
        self.secret = secret.encode()
        self.request_authenticator = None

    def authenticate(self, username, password):
        # Step 1: Create Access-Request packet
        request = self.create_access_request(username, password)
        self.request_authenticator = request[4:20]

        # Step 2: Send request
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(5.0)
        sock.sendto(request, (self.server, 1812))

        # Step 3: Receive response
        try:
            response, _ = sock.recvfrom(4096)
        except socket.timeout:
            return False

        # Fixed: Critical step - verify Response-Authenticator
        if not self.verify_response_authenticator(response):
            # Response is spoofed or corrupted!
            log_security_event("Invalid RADIUS response authenticator")
            return False

        # Step 4: Now safely check the response code
        code = response[0]
        if code == 2:  # Access-Accept
            return True
        elif code == 3:  # Access-Reject
            return False
        else:
            log_security_event(f"Unexpected RADIUS response code: {code}")
            return False

    def verify_response_authenticator(self, response):
        """Verify the response came from the real RADIUS server."""
        if len(response) < 20:
            return False

        code = response[0:1]
        identifier = response[1:2]
        length = response[2:4]
        response_auth = response[4:20]
        attributes = response[20:]

        # Fixed: Calculate expected authenticator using shared secret
        expected = hashlib.md5(
            code + identifier + length + self.request_authenticator +
            attributes + self.secret
        ).digest()

        # Use constant-time comparison
        return hmac.compare_digest(response_auth, expected)
// Fixed: OAuth with proper state verification
public class SecureOAuthClient {

    private String clientId;
    private String clientSecret;
    private String redirectUri;

    public String startAuthorization(HttpSession session) {
        // Step 1: Generate cryptographically secure state
        String state = generateSecureState();
        session.setAttribute("oauth_state", state);

        // Also store creation time for expiry checking
        session.setAttribute("oauth_state_created", System.currentTimeMillis());

        // Step 2: Build authorization URL with state
        String authUrl = String.format(
            "https://auth.example.com/authorize?client_id=%s&redirect_uri=%s&state=%s&response_type=code",
            URLEncoder.encode(clientId, "UTF-8"),
            URLEncoder.encode(redirectUri, "UTF-8"),
            URLEncoder.encode(state, "UTF-8")
        );

        return authUrl;
    }

    public TokenResponse handleCallback(HttpServletRequest request, HttpSession session)
            throws SecurityException {

        String code = request.getParameter("code");
        String returnedState = request.getParameter("state");
        String error = request.getParameter("error");

        // Check for OAuth errors first
        if (error != null) {
            throw new OAuthException("Authorization failed: " + error);
        }

        // Fixed: Critical step - verify state parameter
        String expectedState = (String) session.getAttribute("oauth_state");
        Long stateCreated = (Long) session.getAttribute("oauth_state_created");

        // Clear state immediately to prevent reuse
        session.removeAttribute("oauth_state");
        session.removeAttribute("oauth_state_created");

        // Verify state was set
        if (expectedState == null) {
            throw new SecurityException("No OAuth state in session - possible CSRF");
        }

        // Verify state matches
        if (!MessageDigest.isEqual(
                expectedState.getBytes(), returnedState.getBytes())) {
            throw new SecurityException("State mismatch - CSRF attack detected");
        }

        // Verify state is not too old (5 minute limit)
        if (stateCreated == null ||
            System.currentTimeMillis() - stateCreated > 300000) {
            throw new SecurityException("OAuth state expired");
        }

        // All verifications passed, exchange code for token
        return exchangeCodeForToken(code);
    }

    private String generateSecureState() {
        byte[] bytes = new byte[32];
        new SecureRandom().nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }
}

The fix ensures all critical authentication steps are executed without any omissions.


Exploited in the Wild

RADIUS Authentication Bypass (Network Infrastructure, 2004)

CVE-2004-2163 documented a RADIUS client implementation that failed to verify the shared secret in response packets, allowing attackers to spoof Access-Accept responses and bypass authentication entirely.

Protocol Initialization Bypass (Various Applications, 2005)

CVE-2005-3327 documented an authentication bypass where skipping the first startup step required by the protocol allowed attackers to bypass authentication controls.


Tools to Test/Exploit

  • Wireshark — Network analyzer for examining authentication protocol exchanges.

  • Burp Suite — Web security tool for testing OAuth and web authentication flows.

  • FreeRADIUS — RADIUS server with debugging capabilities for protocol analysis.


CVE Examples

  • CVE-2004-2163 — RADIUS shared secret not verified in response packets.

  • CVE-2005-3327 — Authentication bypass by skipping protocol initialization.

  • CVE-2014-1266 — Apple SSL skipping signature verification step.


References

  1. MITRE Corporation. "CWE-304: Missing Critical Step in Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/304.html

  2. RFC 2865. "Remote Authentication Dial In User Service (RADIUS)." https://tools.ietf.org/html/rfc2865

  3. OWASP Foundation. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html