Unzureichende Verifizierung der Datenauthentizität
Beschreibung
Unzureichende Verifizierung der Datenauthentizität tritt auf, wenn Software den Ursprung oder die Authentizität von Daten nicht ausreichend verifiziert, bevor sie in einer sicherheitskritischen Entscheidung verwendet werden. Dies umfasst das Akzeptieren von Daten ohne Signaturverifizierung, das Vertrauen auf Daten aus nicht authentifizierten Quellen oder das Versäumnis, zu validieren, dass Daten von einem erwarteten Absender stammen. Angreifer können Datenquellen fälschen, Nachrichten fälschen oder zuvor erfasste Daten wiedergeben, um das Anwendungsverhalten zu manipulieren.
Risiko
Ohne Authentizitätsprüfung können Angreifer vertrauenswürdige Quellen imitieren, Nachrichten fälschen oder Daten während der Übertragung manipulieren. Diese Schwachstelle ermöglicht Man-in-the-Middle-Angriffe, Replay-Angriffe und Dateninjection. Systeme, die Daten allein auf Grundlage des Netzwerkstandorts oder der Transportverschlüsselung vertrauen, sind anfällig -- Verschlüsselung stellt Vertraulichkeit sicher, aber ohne zusätzliche Maßnahmen keine Authentizität. API-Endpunkte, die Anfragensignaturen nicht verifizieren, können ausgenutzt werden, um unautorisierte Aktionen auszuführen.
Lösung
Implementieren Sie kryptographische Verifizierung für alle sicherheitskritischen Daten. Verwenden Sie digitale Signaturen (HMAC, RSA, ECDSA) zur Verifizierung von Datenursprung und -integrität. Fügen Sie Zeitstempel oder Nonces ein, um Replay-Angriffe zu verhindern. Verifizieren Sie Zertifikatsketten für TLS-Verbindungen. Implementieren Sie Mutual TLS (mTLS) für Service-zu-Service-Kommunikation. Verwenden Sie authentifizierte Verschlüsselung (AES-GCM) anstelle reiner Verschlüsselungsmodi. Validieren Sie Webhook-Signaturen von Drittanbieterdiensten. Implementieren Sie ordnungsgemäße Ursprungsverifizierung für Cross-Origin-Anfragen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Datenmanipulation Angreifer können Daten ohne Erkennung ändern und den Systemzustand beschädigen. |
| Authentifizierung | Bereich: Spoofing Nicht authentifizierte Daten ermöglichen Angreifern, sich als legitime Quellen auszugeben. |
| Zugriffskontrolle | Bereich: Unautorisierte Aktionen Gefälschte Anfragen können privilegierte Operationen auslösen. |
Beispielcode und Lösung
Verwundbarer Code
# VERWUNDBAR: Webhook ohne Signaturverifizierung akzeptiert
from flask import Flask, request
@app.route('/webhook/payment', methods=['POST'])
def payment_webhook_vulnerable():
data = request.json
# Keine Signaturverifizierung - jeder kann gefälschte Webhooks senden!
payment_id = data['payment_id']
status = data['status']
if status == 'completed':
mark_order_paid(payment_id) # Angreifer kann jede Bestellung als bezahlt markieren!
return '', 200
# VERWUNDBAR: API ohne Anfragensignierung
@app.route('/api/transfer', methods=['POST'])
def transfer_vulnerable():
# Keine Authentifizierung des Anfrageursprungs
data = request.json
# Angreifer kann Anfragen senden und sich als legitim ausgeben
transfer_funds(data['from'], data['to'], data['amount'])
return jsonify({'status': 'success'})
# VERWUNDBAR: Vertrauen auf clientseitig bereitgestellte Daten
@app.route('/api/user/role')
def get_role_vulnerable():
# Rolle vom Client - nicht verifiziert!
role = request.headers.get('X-User-Role')
if role == 'admin':
return get_admin_data()
return get_user_data()
// VERWUNDBAR: Java-Webhook ohne Verifizierung
@RestController
public class VulnerableWebhook {
@PostMapping("/webhook/payment")
public ResponseEntity<?> handlePayment(@RequestBody PaymentEvent event) {
// Keine Signaturverifizierung!
if ("completed".equals(event.getStatus())) {
orderService.markPaid(event.getOrderId());
}
return ResponseEntity.ok().build();
}
// VERWUNDBAR: Deserialisierung ohne Authentizitätsprüfung
@PostMapping("/api/message")
public ResponseEntity<?> processMessage(@RequestBody byte[] data) {
// Keine Verifizierung, dass Daten aus vertrauenswürdiger Quelle stammen!
Message message = deserialize(data);
processMessage(message);
return ResponseEntity.ok().build();
}
}
// VERWUNDBAR: Node.js-Webhook ohne Signatur
app.post('/webhook/github', (req, res) => {
const event = req.body;
// Keine Signaturverifizierung - jeder kann auslösen!
if (event.action === 'push') {
triggerDeployment(event.repository);
}
res.sendStatus(200);
});
// VERWUNDBAR: Tokens ohne Verifizierung akzeptieren
app.get('/api/data', (req, res) => {
const token = req.headers.authorization;
// Prüft nur ob Token existiert, verifiziert ihn nicht!
if (token) {
res.json(getSensitiveData());
} else {
res.status(401).send('Unauthorized');
}
});
Sichere Lösung
# SICHER: Webhook mit Signaturverifizierung
import hmac
import hashlib
from flask import Flask, request, abort
WEBHOOK_SECRET = os.environ['WEBHOOK_SECRET']
def verify_signature(payload, signature, secret):
"""HMAC-Signatur verifizieren."""
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
@app.route('/webhook/payment', methods=['POST'])
def payment_webhook_safe():
# Signatur aus Header holen
signature = request.headers.get('X-Signature')
if not signature:
abort(401, 'Missing signature')
# Signatur verifizieren
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(401, 'Invalid signature')
# Jetzt sicher zu verarbeiten
data = request.json
payment_id = data['payment_id']
status = data['status']
if status == 'completed':
mark_order_paid(payment_id)
return '', 200
# SICHER: API mit Anfragensignierung
import time
from functools import wraps
def verify_request_signature(f):
@wraps(f)
def decorated(*args, **kwargs):
timestamp = request.headers.get('X-Timestamp')
signature = request.headers.get('X-Signature')
api_key = request.headers.get('X-API-Key')
if not all([timestamp, signature, api_key]):
abort(401, 'Missing authentication headers')
# Zeitstempel prüfen um Replay-Angriffe zu verhindern
request_time = int(timestamp)
current_time = int(time.time())
if abs(current_time - request_time) > 300: # 5-Minuten-Fenster
abort(401, 'Request expired')
# Geheimnis für API-Key holen
secret = get_api_secret(api_key)
if not secret:
abort(401, 'Invalid API key')
# Zu signierende Zeichenkette erstellen
string_to_sign = f"{request.method}\n{request.path}\n{timestamp}\n"
if request.data:
string_to_sign += hashlib.sha256(request.data).hexdigest()
# Signatur verifizieren
expected = hmac.new(
secret.encode(),
string_to_sign.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, 'Invalid signature')
return f(*args, **kwargs)
return decorated
@app.route('/api/transfer', methods=['POST'])
@verify_request_signature
def transfer_safe():
data = request.json
transfer_funds(data['from'], data['to'], data['amount'])
return jsonify({'status': 'success'})
# SICHER: GitHub-Webhook-Verifizierung
import hashlib
import hmac
GITHUB_WEBHOOK_SECRET = os.environ['GITHUB_WEBHOOK_SECRET']
@app.route('/webhook/github', methods=['POST'])
def github_webhook_safe():
signature = request.headers.get('X-Hub-Signature-256')
if not signature:
abort(401, 'Missing signature')
# GitHub-Signatur verifizieren
expected = 'sha256=' + hmac.new(
GITHUB_WEBHOOK_SECRET.encode(),
request.data,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, 'Invalid signature')
event = request.json
event_type = request.headers.get('X-GitHub-Event')
if event_type == 'push':
trigger_deployment(event['repository'])
return '', 200
// SICHER: Java-Webhook mit Signaturverifizierung
@RestController
public class SecureWebhook {
@Value("${webhook.secret}")
private String webhookSecret;
@PostMapping("/webhook/payment")
public ResponseEntity<?> handlePayment(
@RequestBody String payload,
@RequestHeader("X-Signature") String signature) {
// Signatur verifizieren
if (!verifySignature(payload, signature)) {
return ResponseEntity.status(401).body("Invalid signature");
}
PaymentEvent event = objectMapper.readValue(payload, PaymentEvent.class);
if ("completed".equals(event.getStatus())) {
orderService.markPaid(event.getOrderId());
}
return ResponseEntity.ok().build();
}
private boolean verifySignature(String payload, String signature) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(webhookSecret.getBytes(), "HmacSHA256");
mac.init(key);
String expected = "sha256=" + bytesToHex(mac.doFinal(payload.getBytes()));
return MessageDigest.isEqual(
expected.getBytes(),
signature.getBytes()
);
} catch (Exception e) {
return false;
}
}
}
// SICHER: Anfragensignierungs-Service
@Service
public class RequestSigningService {
public Map<String, String> signRequest(String method, String path, byte[] body, String apiKey, String secret) {
long timestamp = System.currentTimeMillis() / 1000;
String bodyHash = body != null ?
Hex.encodeHexString(MessageDigest.getInstance("SHA-256").digest(body)) : "";
String stringToSign = String.format("%s\n%s\n%d\n%s", method, path, timestamp, bodyHash);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
String signature = Hex.encodeHexString(mac.doFinal(stringToSign.getBytes()));
return Map.of(
"X-API-Key", apiKey,
"X-Timestamp", String.valueOf(timestamp),
"X-Signature", signature
);
}
public boolean verifyRequest(HttpServletRequest request, String secret) {
String timestamp = request.getHeader("X-Timestamp");
String signature = request.getHeader("X-Signature");
if (timestamp == null || signature == null) {
return false;
}
// Zeitstempel prüfen
long requestTime = Long.parseLong(timestamp);
long currentTime = System.currentTimeMillis() / 1000;
if (Math.abs(currentTime - requestTime) > 300) {
return false; // Abgelaufen
}
// Signatur rekonstruieren und verifizieren
// ... (gleiche Logik wie beim Signieren)
return MessageDigest.isEqual(expected.getBytes(), signature.getBytes());
}
}
// SICHER: Node.js-Webhook mit Signaturverifizierung
const crypto = require('crypto');
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhook/payment', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'];
if (!signature) {
return res.status(401).send('Missing signature');
}
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
if (event.status === 'completed') {
markOrderPaid(event.payment_id);
}
res.sendStatus(200);
});
// SICHER: Stripe-Webhook-Verifizierung
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhook/stripe',
express.raw({ type: 'application/json' }),
async (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// Stripe-Bibliothek übernimmt Verifizierung
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Ereignis ist verifiziert
switch (event.type) {
case 'payment_intent.succeeded':
handlePaymentSuccess(event.data.object);
break;
}
res.json({ received: true });
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
res.status(400).send(`Webhook Error: ${err.message}`);
}
}
);
// SICHER: API-Anfragensignierungs-Middleware
function signedRequestMiddleware(secret) {
return (req, res, next) => {
const timestamp = req.headers['x-timestamp'];
const signature = req.headers['x-signature'];
const apiKey = req.headers['x-api-key'];
if (!timestamp || !signature || !apiKey) {
return res.status(401).json({ error: 'Missing authentication headers' });
}
// Zeitstempel prüfen
const requestTime = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - requestTime) > 300) {
return res.status(401).json({ error: 'Request expired' });
}
// Geheimnis für API-Key holen
const clientSecret = getClientSecret(apiKey);
if (!clientSecret) {
return res.status(401).json({ error: 'Invalid API key' });
}
// Zu signierende Zeichenkette erstellen
let bodyHash = '';
if (req.body && Object.keys(req.body).length > 0) {
bodyHash = crypto
.createHash('sha256')
.update(JSON.stringify(req.body))
.digest('hex');
}
const stringToSign = `${req.method}\n${req.path}\n${timestamp}\n${bodyHash}`;
const expected = crypto
.createHmac('sha256', clientSecret)
.update(stringToSign)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
};
}
app.use('/api/', signedRequestMiddleware());
Ausgenutzt in der Praxis
Webhook-Spoofing-Angriffe
Zahlungsanbieter wie Stripe und PayPal haben Angriffe dokumentiert, bei denen Angreifer gefälschte Webhook-Benachrichtigungen senden, um Bestellungen als bezahlt zu markieren, ohne dass eine tatsächliche Zahlung erfolgt.
API-Replay-Angriffe
Systeme ohne Zeitstempelverifizierung in Signaturen wurden durch erfasste und wiedergegebene Anfragen ausgenutzt.
OAuth-Token-Diebstahl
Anwendungen, die Tokens ohne ordnungsgemäße Validierung akzeptieren, wurden durch gestohlene oder gefälschte Tokens kompromittiert.
Tools zum Testen und Ausnutzen
-
Burp Suite -- Anfragen abfangen und ändern.
-
Postman -- benutzerdefinierte Anfragen ohne Signaturen erstellen.
-
Benutzerdefinierte Skripte zum Wiedergeben erfasster Anfragen.
CVE-Beispiele
-
CVE-2021-41117 -- Umgehung der Authentizitätsverifizierung.
-
CVE-2020-8945 -- GPGME-Signaturverifizierungsumgehung.
-
CVE-2019-1010266 -- Webhook-Signaturumgehung.
Referenzen
-
MITRE. "CWE-345: Insufficient Verification of Data Authenticity." https://cwe.mitre.org/data/definitions/345.html
-
OWASP. "Webhook Security." https://cheatsheetseries.owasp.org/cheatsheets/Webhook_Security_Cheat_Sheet.html