Reflexionsangriff in einem Authentifizierungsprotokoll
Beschreibung
Reflexionsangriff in einem Authentifizierungsprotokoll ist eine Schwachstelle, die auftritt, wenn einfache Authentifizierungsprotokolle mit gemeinsamen geheimen Schlüsseln anfällig für Angriffe sind, bei denen ein Angreifer die Zielmaschine selbst nutzen kann, um gültige Antworten auf Authentifizierungs-Challenges zu generieren. In gegenseitigen Authentifizierungsprotokollen, die denselben vorgeteilten Schlüssel über mehrere Entitäten hinweg verwenden, kann ein Angreifer das Protokolldesign ausnutzen, indem er eine zweite Verbindung zum Server öffnet und ihn bittet, die Challenge aus der ersten Verbindung zu lösen. Wenn der Server die Lösung zurückgibt, verwendet der Angreifer sie in der ursprünglichen Verbindung, um sich ohne den tatsächlichen geheimen Schlüssel zu authentifizieren.
Risiko
Reflexionsangriffe stellen eine grundlegende Schwäche in schlecht entworfenen Challenge-Response-Authentifizierungsprotokollen dar. Das Risiko ist schwerwiegend, da Angreifer sich ohne Kenntnis des gemeinsamen Geheimnisses authentifizieren können -- sie bringen den Server im Wesentlichen dazu, sich selbst zu authentifizieren. Diese Angriffe sind besonders gefährlich in Umgebungen, die symmetrische Schlüsselauthentifizierung verwenden, bei der derselbe Schlüssel für mehrere Zwecke oder von mehreren Parteien genutzt wird. Systeme, die einfache hashbasierte Challenges ohne ordnungsgemäßes Protokolldesign verwenden, sind hochanfällig. Der Angriff erfordert nur Netzwerkzugang und die Möglichkeit, mehrere Verbindungen zu öffnen, was ihn für Angreifer mit bescheidenen Fähigkeiten realisierbar macht. Bei Erfolg erhält der Angreifer vollen authentifizierten Zugang und kann damit potenziell ganze Systeme oder Netzwerke kompromittieren.
Lösung
Entwerfen Sie Authentifizierungsprotokolle, die Reflexionsangriffe durch asymmetrische Challenge-Response-Mechanismen verhindern. Verwenden Sie verschiedene Schlüssel für Initiatoren und Antwortende, um sicherzustellen, dass der Server keine gültigen Antworten auf seine eigenen Challenges generieren kann. Fügen Sie Rollenbezeichner in Challenge-Berechnungen ein, damit Antworten einer Rolle nicht für eine andere gültig sein können. Verlangen Sie, dass der Initiator seine Identität nachweist, bevor der Antwortende Challenges sendet. Implementieren Sie Challenge-Aktualität durch Einbeziehung von Zeitstempeln, Sequenznummern oder Session-Identifikatoren. Verwenden Sie asymmetrische Kryptographie, wo praktikabel, da sie Reflexion von Natur aus verhindert, indem sie verschiedene Schlüssel für jede Partei verwendet. Erwägen Sie die Verwendung etablierter Protokolle wie TLS mit gegenseitiger Authentifizierung anstelle selbst entworfener Protokolle. Beziehen Sie verbindungsspezifische Daten in Authentifizierungsberechnungen ein, um Antworten an bestimmte Sessions zu binden.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Erfolgreiche Reflexionsangriffe ermöglichen es Angreifern, sich als legitime Benutzer zu authentifizieren, ohne über die korrekten Zugangsdaten zu verfügen, und erhalten damit vollen Zugang zu geschützten Ressourcen und Fähigkeiten. |
| Authentifizierung, Nichtabstreitbarkeit | Bereich: Authentifizierung, Nichtabstreitbarkeit Der Authentifizierungsmechanismus wird vollständig umgangen, und Aktionen von Angreifern können dem imitierten legitimen Benutzer zugeschrieben werden, was die Rechenschaftspflicht untergräbt. |
Beispielcode und Lösung
Verwundbarer Code (Python)
Die folgenden Beispiele zeigen Authentifizierung, die anfällig für Reflexionsangriffe ist:
# Verwundbar: Challenge-Response-Protokoll anfällig für Reflexion
import hashlib
import secrets
import socket
class VulnerableAuthServer:
def __init__(self, shared_secret):
self.shared_secret = shared_secret
self.pending_challenges = {}
def generate_challenge(self, connection_id):
# Challenge für Client generieren
challenge = secrets.token_hex(16)
self.pending_challenges[connection_id] = challenge
return challenge
def compute_response(self, challenge):
# Verwundbar: Dieselbe Funktion wird von Client und Server verwendet
# Server berechnet Antwort, wenn er als "Client" gefragt wird
return hashlib.sha256(
(self.shared_secret + challenge).encode()
).hexdigest()
def verify_response(self, connection_id, response):
challenge = self.pending_challenges.get(connection_id)
if not challenge:
return False
expected = self.compute_response(challenge)
return response == expected
def handle_connection(self, conn, conn_id):
# Schritt 1: Challenge an Client senden
challenge = self.generate_challenge(conn_id)
conn.send(f"CHALLENGE:{challenge}".encode())
# Verwundbar: Antwortet auch auf Challenges, die AN ihn gesendet werden
data = conn.recv(1024).decode()
if data.startswith("RESPONSE:"):
# Antwort des Clients verifizieren
response = data.split(":")[1]
if self.verify_response(conn_id, response):
conn.send(b"AUTH_SUCCESS")
else:
conn.send(b"AUTH_FAILED")
elif data.startswith("CHALLENGE:"):
# Verwundbar: Server antwortet auf Challenges!
# Angreifer nutzt dies aus
incoming_challenge = data.split(":")[1]
response = self.compute_response(incoming_challenge)
conn.send(f"RESPONSE:{response}".encode())
# Angriffsszenario:
# 1. Angreifer öffnet Verbindung A
# 2. Server sendet CHALLENGE:abc123 an Angreifer
# 3. Angreifer öffnet Verbindung B, sendet CHALLENGE:abc123
# 4. Server antwortet mit RESPONSE:<hash_von_abc123>
# 5. Angreifer sendet diese Antwort auf Verbindung A
# 6. Server authentifiziert Angreifer auf Verbindung A!
// Verwundbar: Java gegenseitige Authentifizierung mit Reflexionsschwachstelle
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
public class VulnerableAuthProtocol {
private final byte[] sharedSecret;
private final Map<String, String> pendingChallenges = new HashMap<>();
public VulnerableAuthProtocol(byte[] secret) {
this.sharedSecret = secret;
}
// Verwundbar: Eine einzige HMAC-Funktion für alle Rollen
public String computeHMAC(String challenge) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(sharedSecret, "HmacSHA256"));
byte[] result = mac.doFinal(challenge.getBytes());
return Base64.getEncoder().encodeToString(result);
} catch (Exception e) {
return null;
}
}
// Server generiert Challenge
public String serverChallenge(String sessionId) {
String challenge = UUID.randomUUID().toString();
pendingChallenges.put(sessionId, challenge);
return challenge;
}
// Verwundbar: Server antwortet auch auf Challenges (für gegenseitige Auth.)
public String respondToChallenge(String challenge) {
// Dieselbe Funktion, derselbe Schlüssel - ermöglicht Reflexion
return computeHMAC(challenge);
}
public boolean verifyClientResponse(String sessionId, String response) {
String challenge = pendingChallenges.remove(sessionId);
if (challenge == null) return false;
String expected = computeHMAC(challenge);
return expected.equals(response);
}
}
Sichere Lösung (Python)
# Behoben: Protokoll immun gegen Reflexionsangriffe
import hashlib
import hmac
import secrets
import time
class SecureAuthServer:
def __init__(self, shared_secret):
self.shared_secret = shared_secret.encode()
self.pending_challenges = {}
self.used_challenges = set()
def generate_challenge(self, connection_id):
challenge = secrets.token_hex(16)
timestamp = int(time.time())
self.pending_challenges[connection_id] = {
'challenge': challenge,
'timestamp': timestamp
}
return f"{challenge}:{timestamp}"
def compute_client_response(self, challenge, timestamp):
# Behoben: Rollenbezeichner in die Berechnung einbeziehen
# Client verwendet "CLIENT"-Präfix, wodurch die Serverantwort ungültig wird
message = f"CLIENT:{challenge}:{timestamp}"
return hmac.new(
self.shared_secret,
message.encode(),
hashlib.sha256
).hexdigest()
def compute_server_response(self, challenge, timestamp, client_nonce):
# Behoben: Andere Berechnung für Serverantworten
# Beinhaltet Client-Nonce zur Verhinderung von Reflexion
message = f"SERVER:{challenge}:{timestamp}:{client_nonce}"
return hmac.new(
self.shared_secret,
message.encode(),
hashlib.sha256
).hexdigest()
def verify_response(self, connection_id, response, client_nonce):
pending = self.pending_challenges.get(connection_id)
if not pending:
return False
challenge = pending['challenge']
timestamp = pending['timestamp']
# Behoben: Zeitstempelaktualität prüfen
if abs(time.time() - timestamp) > 300: # 5-Minuten-Fenster
return False
# Behoben: Wiederverwendung von Challenges verhindern
if challenge in self.used_challenges:
return False
expected = self.compute_client_response(challenge, timestamp)
if not hmac.compare_digest(response, expected):
return False
self.used_challenges.add(challenge)
del self.pending_challenges[connection_id]
return True
def handle_connection(self, conn, conn_id):
# Schritt 1: Client-Nonce zuerst empfangen (Client beweist Initiative)
data = conn.recv(1024).decode()
if not data.startswith("CLIENT_NONCE:"):
conn.send(b"PROTOCOL_ERROR")
return
client_nonce = data.split(":")[1]
# Schritt 2: Challenge einschließlich Client-Nonce senden
challenge_data = self.generate_challenge(conn_id)
conn.send(f"CHALLENGE:{challenge_data}".encode())
# Schritt 3: Client-Antwort empfangen und verifizieren
data = conn.recv(1024).decode()
if data.startswith("RESPONSE:"):
response = data.split(":")[1]
if self.verify_response(conn_id, response, client_nonce):
# Serverantwort für gegenseitige Auth. senden
pending = self.pending_challenges.get(conn_id, {})
server_response = self.compute_server_response(
pending.get('challenge', ''),
pending.get('timestamp', 0),
client_nonce
)
conn.send(f"AUTH_SUCCESS:{server_response}".encode())
else:
conn.send(b"AUTH_FAILED")
// Behoben: Java-Authentifizierung immun gegen Reflexionsangriffe
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
import java.security.SecureRandom;
public class SecureAuthProtocol {
private static final String CLIENT_ROLE = "CLIENT";
private static final String SERVER_ROLE = "SERVER";
private final byte[] sharedSecret;
private final Map<String, ChallengeData> pendingChallenges = new HashMap<>();
private final Set<String> usedChallenges = Collections.synchronizedSet(new HashSet<>());
public SecureAuthProtocol(byte[] secret) {
this.sharedSecret = secret;
}
// Behoben: Rollenspezifische HMAC-Berechnung
private String computeRoleHMAC(String role, String challenge,
String timestamp, String nonce) {
try {
// Behoben: Rolle in die Berechnung einbeziehen
String message = String.format("%s:%s:%s:%s",
role, challenge, timestamp, nonce);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(sharedSecret, "HmacSHA256"));
byte[] result = mac.doFinal(message.getBytes());
return Base64.getEncoder().encodeToString(result);
} catch (Exception e) {
return null;
}
}
public String computeClientResponse(String challenge,
String timestamp, String nonce) {
// Client-Rolle - kann nicht als Server-Antwort verwendet werden
return computeRoleHMAC(CLIENT_ROLE, challenge, timestamp, nonce);
}
public String computeServerResponse(String challenge,
String timestamp, String clientNonce) {
// Server-Rolle mit Client-Nonce - kann nicht reflektiert werden
return computeRoleHMAC(SERVER_ROLE, challenge, timestamp, clientNonce);
}
public ChallengeData serverChallenge(String sessionId, String clientNonce) {
String challenge = UUID.randomUUID().toString();
String timestamp = String.valueOf(System.currentTimeMillis());
ChallengeData data = new ChallengeData(challenge, timestamp, clientNonce);
pendingChallenges.put(sessionId, data);
return data;
}
public boolean verifyClientResponse(String sessionId, String response) {
ChallengeData data = pendingChallenges.get(sessionId);
if (data == null) return false;
// Behoben: Wiederverwendung von Challenges verhindern
if (usedChallenges.contains(data.challenge)) {
return false;
}
// Behoben: Zeitstempelaktualität prüfen
long challengeTime = Long.parseLong(data.timestamp);
if (System.currentTimeMillis() - challengeTime > 300000) {
return false;
}
String expected = computeClientResponse(
data.challenge, data.timestamp, data.clientNonce);
if (expected.equals(response)) {
usedChallenges.add(data.challenge);
return true;
}
return false;
}
}
Die Behebung verwendet rollenspezifische Berechnungen und schließt vom Client bereitgestellte Nonces ein, um Reflexionsangriffe zu verhindern.
Ausgenutzt in der Praxis
SMB-Relay-Angriffe (Windows-Netzwerke, fortlaufend)
SMB-Relay-Angriffe nutzen Reflexionsschwachstellen in der NTLM-Authentifizierung aus und ermöglichen es Angreifern, Authentifizierungsanfragen zwischen Servern weiterzuleiten und unautorisierten Zugang zu Netzwerkressourcen zu erhalten.
NTLM-Reflexion (Windows, 2008)
CVE-2008-4037 dokumentierte NTLM-Reflexionsschwachstellen, bei denen Windows-Systeme dazu gebracht werden könnten, sich selbst zu authentifizieren, was eine lokale Rechteeskalation ermöglichte.
Tools zum Testen und Ausnutzen
-
Responder -- LLMNR/NBT-NS/mDNS-Poisoner mit integrierten NTLM-Relay-Fähigkeiten.
-
ntlmrelayx -- Tool für NTLM-Relay-Angriffe als Teil der Impacket-Suite.
-
Burp Suite -- Web-Sicherheitstool zum Testen von Authentifizierungsprotokoll-Schwachstellen.
CVE-Beispiele
-
CVE-2005-3435 -- Authentifizierung via MD5-Hash-Vergleich anfällig für Replay und Reflexion.
-
CVE-2008-4037 -- Windows NTLM-Reflexion ermöglicht lokale Rechteeskalation.
-
CVE-2019-1040 -- Windows NTLM-Manipulationsschwachstelle ermöglicht Relay-Angriffe.
Referenzen
-
MITRE Corporation. "CWE-301: Reflection Attack in an Authentication Protocol." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/301.html
-
OWASP Foundation. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
-
Lowe, J. "A Taxonomy of DDoS Attacks and DDoS Defense Mechanisms." https://www.cs.virginia.edu/~cs757/papers/DDoS-taxonomy.pdf