Race Condition During Access to Alternate Channel
Description
Race Condition During Access to Alternate Channel occurs when a product establishes an alternate communication channel meant for authorized users, but fails to restrict access to other actors, creating a race condition vulnerability. This creates a race condition that allows an attacker to access the channel before the authorized user does. The vulnerability combines improper channel protection with timing-based exploitation, where attackers can intercept or hijack connections intended for legitimate users.
Risk
Race conditions in alternate channels have severe implications. Connection hijacking. Session theft. Port stealing. Man-in-the-middle attacks. Privilege escalation. Authentication bypass. Data interception. High likelihood in network services with predictable port allocation.
Solution
Implement proper access controls on all communication channels during architecture and design phase. Use cryptographic authentication for channel access. Avoid predictable port allocation. Implement connection binding with strong identifiers. Use time-limited tokens for channel access during implementation phase.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - Attackers can hijack channels intended for authorized users. |
| Authorization | Scope: Authorization Bypass Protection Mechanism - Authentication can be bypassed by winning the race condition. |
Example Code
Vulnerable Code
// Vulnerable: FTP data channel race condition (Pizza Thief vulnerability)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// VULNERABLE: Classic FTP PORT command race condition
void vulnerable_ftp_data_channel(int control_fd, const char* client_ip) {
struct sockaddr_in data_addr;
int data_fd;
int port;
// Server opens a port for data transfer
data_fd = socket(AF_INET, SOCK_STREAM, 0);
data_addr.sin_family = AF_INET;
data_addr.sin_addr.s_addr = INADDR_ANY;
data_addr.sin_port = 0; // Let OS assign port
bind(data_fd, (struct sockaddr*)&data_addr, sizeof(data_addr));
listen(data_fd, 1);
// Get assigned port
socklen_t len = sizeof(data_addr);
getsockname(data_fd, (struct sockaddr*)&data_addr, &len);
port = ntohs(data_addr.sin_port);
// VULNERABLE: Tell client the port number
char response[100];
snprintf(response, sizeof(response),
"227 Entering Passive Mode (127,0,0,1,%d,%d)\r\n",
port / 256, port % 256);
send(control_fd, response, strlen(response), 0);
// VULNERABLE: Accept ANY connection on this port
// Attacker can connect before legitimate client
struct sockaddr_in peer_addr;
socklen_t peer_len = sizeof(peer_addr);
int client_data_fd = accept(data_fd, (struct sockaddr*)&peer_addr, &peer_len);
// VULNERABLE: No verification that connection is from legitimate client
// Attacker's connection is accepted instead
// Transfer sensitive data to attacker!
send(client_data_fd, sensitive_file_data, data_len, 0);
close(client_data_fd);
close(data_fd);
}
// VULNERABLE: Named pipe race condition
void vulnerable_named_pipe() {
const char* pipe_name = "\\\\.\\pipe\\myapp_auth";
// VULNERABLE: Create pipe with weak security
HANDLE pipe = CreateNamedPipe(
pipe_name,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, // Only one instance
1024, 1024,
0,
NULL // VULNERABLE: No security descriptor!
);
// Wait for connection
// VULNERABLE: Anyone can connect first
ConnectNamedPipe(pipe, NULL);
// Attacker connects before legitimate client
// Receives authentication data meant for authorized process
}
# Vulnerable: Python callback server with race condition
import socket
import threading
import time
class VulnerableCallbackServer:
def __init__(self):
self.pending_callbacks = {}
def request_callback(self, user_id):
"""User requests a callback port for sensitive data."""
# Create a temporary socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 0)) # Random port
sock.listen(1)
port = sock.getsockname()[1]
# VULNERABLE: Store callback with no secret
self.pending_callbacks[user_id] = {
'socket': sock,
'port': port
}
# Tell user the port
return port # User gets port number
def handle_callback(self, user_id):
"""Wait for callback connection."""
if user_id not in self.pending_callbacks:
return None
callback = self.pending_callbacks[user_id]
sock = callback['socket']
# VULNERABLE: Accept ANY connection
# No way to verify it's the legitimate user
conn, addr = sock.accept()
# VULNERABLE: Race condition - attacker may connect first
# Send sensitive data to whoever connects
conn.send(get_sensitive_data(user_id))
conn.close()
sock.close()
del self.pending_callbacks[user_id]
# Attack scenario:
# 1. Attacker monitors for new listening ports
# 2. When port opens, immediately connect
# 3. Receive data intended for legitimate user
# VULNERABLE: Database connection with predictable temp port
def vulnerable_db_callback(db_host, user_query):
# Request async query result
callback_port = random.randint(10000, 60000) # VULNERABLE: Predictable range
# Open port for callback
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', callback_port))
sock.listen(1)
# Tell database to send results here
db.execute(f"NOTIFY {callback_port} WITH QUERY '{user_query}'")
# VULNERABLE: Accept first connection
conn, addr = sock.accept()
# VULNERABLE: No verification that it's from db_host
result = conn.recv(65536)
return result
// Vulnerable: Java RMI callback race condition
import java.rmi.*;
import java.rmi.server.*;
public class VulnerableRMICallback {
// VULNERABLE: Callback interface exposed
public interface DataCallback extends Remote {
void receiveData(byte[] data) throws RemoteException;
}
// Client implementation
public class VulnerableClient {
public void requestData(RemoteServer server) throws Exception {
// Create callback object
DataCallback callback = new DataCallbackImpl();
// VULNERABLE: Export on random port
UnicastRemoteObject.exportObject(callback, 0);
// VULNERABLE: Register callback - server will call back
server.registerCallback(callback);
// Race condition: Attacker can discover the callback port
// and send malicious data before real server responds
}
}
// VULNERABLE: Server sends data without verifying callback authenticity
public class VulnerableServer extends UnicastRemoteObject
implements RemoteServer {
private List<DataCallback> callbacks = new ArrayList<>();
public void registerCallback(DataCallback callback) {
// VULNERABLE: No verification of callback source
callbacks.add(callback);
}
public void notifyCallbacks(byte[] data) {
for (DataCallback cb : callbacks) {
try {
// VULNERABLE: Could be attacker's fake callback
cb.receiveData(data);
} catch (RemoteException e) {
// Handle error
}
}
}
}
}
Fixed Code
// Fixed: Secure FTP data channel with proper verification
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/rand.h>
// FIXED: FTP data channel with client verification
void secure_ftp_data_channel(int control_fd, const char* expected_client_ip,
uint32_t expected_client_addr) {
struct sockaddr_in data_addr;
int data_fd;
int port;
data_fd = socket(AF_INET, SOCK_STREAM, 0);
data_addr.sin_family = AF_INET;
data_addr.sin_addr.s_addr = INADDR_ANY;
data_addr.sin_port = 0;
bind(data_fd, (struct sockaddr*)&data_addr, sizeof(data_addr));
listen(data_fd, 1);
socklen_t len = sizeof(data_addr);
getsockname(data_fd, (struct sockaddr*)&data_addr, &len);
port = ntohs(data_addr.sin_port);
// FIXED: Generate one-time token
unsigned char token[16];
RAND_bytes(token, sizeof(token));
// Send port and token to client
char response[200];
snprintf(response, sizeof(response),
"227 Entering Passive Mode (...) TOKEN=%s\r\n",
bytes_to_hex(token, sizeof(token)));
send(control_fd, response, strlen(response), 0);
// FIXED: Set timeout for accept
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt(data_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
struct sockaddr_in peer_addr;
socklen_t peer_len = sizeof(peer_addr);
int client_data_fd = accept(data_fd, (struct sockaddr*)&peer_addr, &peer_len);
if (client_data_fd < 0) {
close(data_fd);
return; // Timeout or error
}
// FIXED: Verify peer IP matches expected client
if (peer_addr.sin_addr.s_addr != expected_client_addr) {
close(client_data_fd);
close(data_fd);
send(control_fd, "425 Connection from wrong IP\r\n", 30, 0);
return;
}
// FIXED: Require token verification over data channel
unsigned char received_token[16];
if (recv(client_data_fd, received_token, sizeof(received_token), 0) != sizeof(received_token) ||
memcmp(token, received_token, sizeof(token)) != 0) {
close(client_data_fd);
close(data_fd);
send(control_fd, "425 Token verification failed\r\n", 31, 0);
return;
}
// Now safe to transfer data
send(client_data_fd, file_data, data_len, 0);
close(client_data_fd);
close(data_fd);
}
// FIXED: Named pipe with security descriptor
void secure_named_pipe() {
const char* pipe_name = "\\\\.\\pipe\\myapp_auth";
// FIXED: Create security descriptor
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
// FIXED: Only allow specific user/group
SetSecurityDescriptorDacl(&sd, TRUE, create_restricted_acl(), FALSE);
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
HANDLE pipe = CreateNamedPipe(
pipe_name,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1,
1024, 1024,
0,
&sa // FIXED: Security descriptor
);
ConnectNamedPipe(pipe, NULL);
// FIXED: Verify client identity
ULONG client_pid;
GetNamedPipeClientProcessId(pipe, &client_pid);
if (!is_authorized_process(client_pid)) {
DisconnectNamedPipe(pipe);
CloseHandle(pipe);
return;
}
// Process authenticated connection
}
# Fixed: Python callback server with proper authentication
import socket
import secrets
import hmac
import hashlib
import time
class SecureCallbackServer:
def __init__(self, secret_key):
self.secret_key = secret_key
self.pending_callbacks = {}
def request_callback(self, user_id, client_ip):
"""User requests a callback with authentication token."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# FIXED: Bind to specific interface if possible
sock.bind(('0.0.0.0', 0))
sock.listen(1)
# FIXED: Set timeout
sock.settimeout(10.0)
port = sock.getsockname()[1]
# FIXED: Generate secure one-time token
token = secrets.token_bytes(32)
# FIXED: Store expected client info
self.pending_callbacks[user_id] = {
'socket': sock,
'port': port,
'token': token,
'expected_ip': client_ip,
'created': time.time()
}
# Return port and token (token should be sent securely)
return port, token.hex()
def handle_callback(self, user_id):
"""Wait for authenticated callback connection."""
if user_id not in self.pending_callbacks:
return None
callback = self.pending_callbacks[user_id]
# FIXED: Check for expired callback
if time.time() - callback['created'] > 30:
self._cleanup_callback(user_id)
return None
sock = callback['socket']
try:
conn, addr = sock.accept()
# FIXED: Verify source IP
if addr[0] != callback['expected_ip']:
conn.close()
raise SecurityError("Connection from unexpected IP")
# FIXED: Require token authentication
received_token = conn.recv(32)
if not hmac.compare_digest(received_token, callback['token']):
conn.close()
raise SecurityError("Invalid token")
# FIXED: Now safe to send data
conn.send(get_sensitive_data(user_id))
return True
except socket.timeout:
return None
finally:
self._cleanup_callback(user_id)
def _cleanup_callback(self, user_id):
if user_id in self.pending_callbacks:
self.pending_callbacks[user_id]['socket'].close()
del self.pending_callbacks[user_id]
# FIXED: Secure database callback
def secure_db_callback(db_host, user_query, db_shared_secret):
# FIXED: Generate unpredictable token
callback_token = secrets.token_hex(32)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# FIXED: Bind to localhost only if possible
sock.bind(('127.0.0.1', 0))
sock.listen(1)
sock.settimeout(30.0)
callback_port = sock.getsockname()[1]
# FIXED: Include authentication in callback request
db.execute(f"NOTIFY {callback_port} WITH TOKEN {callback_token} QUERY '{user_query}'")
try:
conn, addr = sock.accept()
# FIXED: Verify connection is from database host
if addr[0] != db_host:
conn.close()
raise SecurityError("Connection not from database")
# FIXED: First message must be the token
received_token = conn.recv(64).decode()
if received_token != callback_token:
conn.close()
raise SecurityError("Invalid callback token")
result = conn.recv(65536)
return result
finally:
sock.close()
CVE Examples
- CVE-1999-0351: FTP "Pizza Thief" vulnerability allowing attackers to hijack data channel ports.
- CVE-2003-0230: Windows named pipe hijacking during authentication.
- CVE-2004-1002: Race condition in temporary file creation allowing symlink attacks.
Related CWEs
- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization (parent)
- CWE-420: Unprotected Alternate Channel (parent)
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition (related)
References
- MITRE Corporation. "CWE-421: Race Condition During Access to Alternate Channel." https://cwe.mitre.org/data/definitions/421.html
- CERT. "FTP Bounce Attack and Port Stealing"
- Microsoft. "Named Pipe Security"