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

AuswirkungDetails
IntegritätBereich: Datenmanipulation

Angreifer können Daten ohne Erkennung ändern und den Systemzustand beschädigen.
AuthentifizierungBereich: Spoofing

Nicht authentifizierte Daten ermöglichen Angreifern, sich als legitime Quellen auszugeben.
ZugriffskontrolleBereich: 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


Referenzen

  1. MITRE. "CWE-345: Insufficient Verification of Data Authenticity." https://cwe.mitre.org/data/definitions/345.html

  2. OWASP. "Webhook Security." https://cheatsheetseries.owasp.org/cheatsheets/Webhook_Security_Cheat_Sheet.html