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
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Missing authentication steps allow attackers to bypass protection mechanisms entirely, gaining unauthorized access to protected resources, user accounts, and system functionality. |
| Integrity, Confidentiality | Scope: 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
-
MITRE Corporation. "CWE-304: Missing Critical Step in Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/304.html
-
RFC 2865. "Remote Authentication Dial In User Service (RADIUS)." https://tools.ietf.org/html/rfc2865
-
OWASP Foundation. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html