Race Condition beim Zugriff auf Alternativkanal
Beschreibung
Race Condition beim Zugriff auf Alternativkanal tritt auf, wenn ein Produkt einen alternativen Kommunikationskanal für autorisierte Benutzer einrichtet, aber den Zugriff auf andere Akteure nicht einschränkt, wodurch eine Race-Condition-Schwachstelle entsteht. Dies erzeugt eine Race Condition, die einem Angreifer ermöglicht, auf den Kanal zuzugreifen, bevor der autorisierte Benutzer dies tut. Die Schwachstelle kombiniert unsachgemäßen Kanalschutz mit timing-basierter Ausnutzung, bei der Angreifer Verbindungen abfangen oder kapern können, die für legitime Benutzer bestimmt sind.
Risiko
Race Conditions in Alternativkanalen haben schwerwiegende Auswirkungen. Verbindungskapern. Sitzungsdiebstahl. Port-Stehlen. Man-in-the-Middle-Angriffe. Privilegieneskalation. Authentifizierungsumgehung. Datenabfangen. Hohe Wahrscheinlichkeit bei Netzwerkdiensten mit vorhersagbarer Port-Zuweisung.
Lösung
Implementieren Sie ordnungsgemäße Zugriffskontrollen auf allen Kommunikationskanalen während der Architektur- und Entwurfsphase. Verwenden Sie kryptographische Authentifizierung für Kanalzugriff. Vermeiden Sie vorhersagbare Port-Zuweisung. Implementieren Sie Verbindungsbindung mit starken Identifikatoren. Verwenden Sie zeitbegrenzte Tokens für Kanalzugriff während der Implementierungsphase.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Zugriffskontrolle Privilegien erlangen oder Identität annehmen - Angreifer können Kanäle kapern, die für autorisierte Benutzer bestimmt sind. |
| Autorisierung | Umfang: Autorisierung Schutzmechanismus umgehen - Authentifizierung kann durch Gewinnen der Race Condition umgangen werden. |
Beispielcode
Anfälliger Code
// Anfällig: FTP Datenkanal Race Condition (Pizza-Dieb-Schwachstelle)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// ANFÄLLIG: Klassische FTP PORT-Befehl 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 öffnet einen Port für Datenübertragung
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; // Betriebssystem weist Port zu
bind(data_fd, (struct sockaddr*)&data_addr, sizeof(data_addr));
listen(data_fd, 1);
// Zugewiesenen Port abrufen
socklen_t len = sizeof(data_addr);
getsockname(data_fd, (struct sockaddr*)&data_addr, &len);
port = ntohs(data_addr.sin_port);
// ANFÄLLIG: Dem Client die Port-Nummer mitteilen
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);
// ANFÄLLIG: JEDE Verbindung auf diesem Port akzeptieren
// Angreifer kann sich vor dem legitimen Client verbinden
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);
// ANFÄLLIG: Keine Verifikation, dass Verbindung vom legitimen Client kommt
// Angreifers Verbindung wird stattdessen akzeptiert
// Sensible Daten an Angreifer übertragen!
send(client_data_fd, sensitive_file_data, data_len, 0);
close(client_data_fd);
close(data_fd);
}
// ANFÄLLIG: Named-Pipe Race Condition
void vulnerable_named_pipe() {
const char* pipe_name = "\\\\.\\pipe\\myapp_auth";
// ANFÄLLIG: Pipe mit schwacher Sicherheit erstellen
HANDLE pipe = CreateNamedPipe(
pipe_name,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, // Nur eine Instanz
1024, 1024,
0,
NULL // ANFÄLLIG: Kein Security-Deskriptor!
);
// Auf Verbindung warten
// ANFÄLLIG: Jeder kann sich zuerst verbinden
ConnectNamedPipe(pipe, NULL);
// Angreifer verbindet sich vor dem legitimen Client
// Empfängt Authentifizierungsdaten, die für autorisierten Prozess bestimmt sind
}
# Anfällig: Python Callback-Server mit Race Condition
import socket
import threading
import time
class VulnerableCallbackServer:
def __init__(self):
self.pending_callbacks = {}
def request_callback(self, user_id):
"""Benutzer fordert einen Callback-Port für sensible Daten an."""
# Temporären Socket erstellen
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 0)) # Zufälliger Port
sock.listen(1)
port = sock.getsockname()[1]
# ANFÄLLIG: Callback ohne Geheimnis speichern
self.pending_callbacks[user_id] = {
'socket': sock,
'port': port
}
# Benutzer erhält Port-Nummer
return port
def handle_callback(self, user_id):
"""Auf Callback-Verbindung warten."""
if user_id not in self.pending_callbacks:
return None
callback = self.pending_callbacks[user_id]
sock = callback['socket']
# ANFÄLLIG: JEDE Verbindung akzeptieren
# Keine Möglichkeit zu verifizieren, dass es der legitime Benutzer ist
conn, addr = sock.accept()
# ANFÄLLIG: Race Condition - Angreifer kann sich zuerst verbinden
# Sensible Daten an jeden senden, der sich verbindet
conn.send(get_sensitive_data(user_id))
conn.close()
sock.close()
del self.pending_callbacks[user_id]
# Angriffsszenario:
# 1. Angreifer überwacht neue lauschende Ports
# 2. Wenn Port öffnet, sofort verbinden
# 3. Daten empfangen, die für legitimen Benutzer bestimmt sind
# ANFÄLLIG: Datenbankverbindung mit vorhersagbarem Temp-Port
def vulnerable_db_callback(db_host, user_query):
# Async-Abfrageergebnis anfordern
callback_port = random.randint(10000, 60000) # ANFÄLLIG: Vorhersagbarer Bereich
# Port für Callback öffnen
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', callback_port))
sock.listen(1)
# Datenbank mitteilen, dass Ergebnisse hierher gesendet werden sollen
db.execute(f"NOTIFY {callback_port} WITH QUERY '{user_query}'")
# ANFÄLLIG: Erste Verbindung akzeptieren
conn, addr = sock.accept()
# ANFÄLLIG: Keine Verifikation, dass es von db_host kommt
result = conn.recv(65536)
return result
// Anfällig: Java RMI Callback Race Condition
import java.rmi.*;
import java.rmi.server.*;
public class VulnerableRMICallback {
// ANFÄLLIG: Callback-Interface offengelegt
public interface DataCallback extends Remote {
void receiveData(byte[] data) throws RemoteException;
}
// Client-Implementierung
public class VulnerableClient {
public void requestData(RemoteServer server) throws Exception {
// Callback-Objekt erstellen
DataCallback callback = new DataCallbackImpl();
// ANFÄLLIG: Auf zufälligem Port exportieren
UnicastRemoteObject.exportObject(callback, 0);
// ANFÄLLIG: Callback registrieren - Server wird zurückrufen
server.registerCallback(callback);
// Race Condition: Angreifer kann den Callback-Port entdecken
// und bösartige Daten senden, bevor echter Server antwortet
}
}
// ANFÄLLIG: Server sendet Daten ohne Callback-Authentizität zu verifizieren
public class VulnerableServer extends UnicastRemoteObject
implements RemoteServer {
private List<DataCallback> callbacks = new ArrayList<>();
public void registerCallback(DataCallback callback) {
// ANFÄLLIG: Keine Verifikation der Callback-Quelle
callbacks.add(callback);
}
public void notifyCallbacks(byte[] data) {
for (DataCallback cb : callbacks) {
try {
// ANFÄLLIG: Könnte gefälschter Callback des Angreifers sein
cb.receiveData(data);
} catch (RemoteException e) {
// Fehler behandeln
}
}
}
}
}
Korrigierter Code
// Korrigiert: Sicherer FTP Datenkanal mit ordnungsgemäßer Verifikation
#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>
// KORRIGIERT: FTP Datenkanal mit Client-Verifikation
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);
// KORRIGIERT: Einmal-Token generieren
unsigned char token[16];
RAND_bytes(token, sizeof(token));
// Port und Token an Client senden
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);
// KORRIGIERT: Timeout für accept setzen
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 oder Fehler
}
// KORRIGIERT: Peer-IP muss erwartetem Client entsprechen
if (peer_addr.sin_addr.s_addr != expected_client_addr) {
close(client_data_fd);
close(data_fd);
send(control_fd, "425 Verbindung von falscher IP\r\n", 32, 0);
return;
}
// KORRIGIERT: Token-Verifikation über Datenkanal erfordern
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-Verifikation fehlgeschlagen\r\n", 39, 0);
return;
}
// Jetzt sicher Daten übertragen
send(client_data_fd, file_data, data_len, 0);
close(client_data_fd);
close(data_fd);
}
// KORRIGIERT: Named-Pipe mit Security-Deskriptor
void secure_named_pipe() {
const char* pipe_name = "\\\\.\\pipe\\myapp_auth";
// KORRIGIERT: Security-Deskriptor erstellen
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
// KORRIGIERT: Nur bestimmte Benutzer/Gruppe erlauben
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 // KORRIGIERT: Security-Deskriptor
);
ConnectNamedPipe(pipe, NULL);
// KORRIGIERT: Client-Identität verifizieren
ULONG client_pid;
GetNamedPipeClientProcessId(pipe, &client_pid);
if (!is_authorized_process(client_pid)) {
DisconnectNamedPipe(pipe);
CloseHandle(pipe);
return;
}
// Authentifizierte Verbindung verarbeiten
}
# Korrigiert: Python Callback-Server mit ordnungsgemäßer Authentifizierung
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):
"""Benutzer fordert Callback mit Authentifizierungstoken an."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# KORRIGIERT: An spezifisches Interface binden wenn möglich
sock.bind(('0.0.0.0', 0))
sock.listen(1)
# KORRIGIERT: Timeout setzen
sock.settimeout(10.0)
port = sock.getsockname()[1]
# KORRIGIERT: Sicheres Einmal-Token generieren
token = secrets.token_bytes(32)
# KORRIGIERT: Erwartete Client-Infos speichern
self.pending_callbacks[user_id] = {
'socket': sock,
'port': port,
'token': token,
'expected_ip': client_ip,
'created': time.time()
}
# Port und Token zurückgeben (Token sollte sicher gesendet werden)
return port, token.hex()
def handle_callback(self, user_id):
"""Auf authentifizierte Callback-Verbindung warten."""
if user_id not in self.pending_callbacks:
return None
callback = self.pending_callbacks[user_id]
# KORRIGIERT: Auf abgelaufenen Callback prüfen
if time.time() - callback['created'] > 30:
self._cleanup_callback(user_id)
return None
sock = callback['socket']
try:
conn, addr = sock.accept()
# KORRIGIERT: Quell-IP verifizieren
if addr[0] != callback['expected_ip']:
conn.close()
raise SecurityError("Verbindung von unerwarteter IP")
# KORRIGIERT: Token-Authentifizierung erfordern
received_token = conn.recv(32)
if not hmac.compare_digest(received_token, callback['token']):
conn.close()
raise SecurityError("Ungültiges Token")
# KORRIGIERT: Jetzt sicher Daten senden
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]
# KORRIGIERT: Sicherer Datenbank-Callback
def secure_db_callback(db_host, user_query, db_shared_secret):
# KORRIGIERT: Unvorhersagbares Token generieren
callback_token = secrets.token_hex(32)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# KORRIGIERT: Nur an localhost binden wenn möglich
sock.bind(('127.0.0.1', 0))
sock.listen(1)
sock.settimeout(30.0)
callback_port = sock.getsockname()[1]
# KORRIGIERT: Authentifizierung in Callback-Anfrage einschließen
db.execute(f"NOTIFY {callback_port} WITH TOKEN {callback_token} QUERY '{user_query}'")
try:
conn, addr = sock.accept()
# KORRIGIERT: Verbindung muss vom Datenbank-Host kommen
if addr[0] != db_host:
conn.close()
raise SecurityError("Verbindung nicht von Datenbank")
# KORRIGIERT: Erste Nachricht muss das Token sein
received_token = conn.recv(64).decode()
if received_token != callback_token:
conn.close()
raise SecurityError("Ungültiges Callback-Token")
result = conn.recv(65536)
return result
finally:
sock.close()
CVE-Beispiele
- CVE-1999-0351: FTP "Pizza-Dieb"-Schwachstelle ermöglicht Angreifern das Kapern von Datenkanal-Ports.
- CVE-2003-0230: Windows Named-Pipe-Kapern während Authentifizierung.
- CVE-2004-1002: Race Condition bei temporärer Dateierstellung ermöglicht Symlink-Angriffe.
Verwandte CWEs
- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization (Eltern)
- CWE-420: Unprotected Alternate Channel (Eltern)
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition (verwandt)
Referenzen
- 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"