Unsachgemäße Verifizierung kryptografischer Signaturen

Beschreibung

Unsachgemäße Verifizierung kryptografischer Signaturen tritt auf, wenn Software die kryptografische Signatur für Daten nicht verifiziert oder falsch verifiziert. Dies ermöglicht Angreifern, signierte Daten zu modifizieren, Authentifizierungsmechanismen zu umgehen oder vertrauenswürdige Entitäten zu imitieren. Häufige Szenarien umfassen das Versäumnis, JWT-Signaturen zu verifizieren, das Akzeptieren von "none"-Algorithmen, das Nicht-Validieren von Zertifikatsketten oder die Verwendung schwacher Signatur-Verifizierungslogik, die umgangen werden kann.

Risiko

Das Versäumnis, Signaturen ordnungsgemäß zu verifizieren, untergräbt das gesamte Vertrauensmodell kryptografischer Systeme. Angreifer können Authentifizierungs-Tokens fälschen (wie JWTs mit "none"-Algorithmus), signierte Software-Updates manipulieren, digital signierte Dokumente modifizieren oder Man-in-the-Middle-Angriffe auf TLS-Verbindungen durchführen. Die 2017er JWT-"none"-Algorithmus-Schwachstellen betrafen zahlreiche Authentifizierungsbibliotheken. Unsachgemäße Zertifikatsvalidierung hat zu unzähligen SSL/TLS-Bypass-Angriffen geführt.

Lösung

Verifizieren Sie kryptografische Signaturen immer bevor Sie signierten Daten vertrauen. Für JWTs spezifizieren Sie explizit erlaubte Algorithmen und lehnen Sie "none" ab. Validieren Sie die gesamte Zertifikatskette für TLS/SSL. Verwenden Sie gut getestete kryptografische Bibliotheken anstatt Verifizierungslogik manuell zu implementieren. Implementieren Sie ordnungsgemäße Fehlerbehandlung, die bei Verifizierungsfehlern geschlossen fehlschlägt. Für Software-Updates verifizieren Sie Signaturen vor der Installation. Verwenden Sie starke Signaturalgorithmen (RSA-2048+, ECDSA mit P-256+).

Häufige Auswirkungen

AuswirkungDetails
IntegritätUmfang: Datenmanipulation

Unsignierte oder unsachgemäß verifizierte Daten können von Angreifern ohne Erkennung modifiziert werden.
AuthentifizierungUmfang: Authentifizierungs-Bypass

Gefälschte Signaturen ermöglichen Angreifern, legitime Benutzer oder Systeme zu imitieren.
ZugriffskontrolleUmfang: Privilegien-Eskalation

Manipulierte Tokens oder Zertifikate können unbefugte Zugriffsstufen gewähren.

Beispielcode + Korrigierter Code

Anfälliger Code

# ANFÄLLIG: JWT-Verifizierung ohne Algorithmus-Prüfung
import jwt

def verify_token_vulnerable(token):
    # Angreifer kann Algorithmus auf "none" ändern und Tokens fälschen!
    payload = jwt.decode(token, options={"verify_signature": False})
    return payload

# ANFÄLLIG: Jeden Algorithmus akzeptieren
def verify_token_any_algo(token, secret):
    # Angreifer kann von RS256 auf HS256 wechseln und mit öffentlichem Schlüssel signieren
    payload = jwt.decode(token, secret)  # Keine Algorithmus-Einschränkung!
    return payload

# ANFÄLLIG: Signatur überhaupt nicht verifizieren
def parse_jwt_no_verify(token):
    import base64
    import json

    parts = token.split('.')
    payload = json.loads(base64.b64decode(parts[1] + '=='))
    # Signatur (parts[2]) wird komplett ignoriert!
    return payload
// ANFÄLLIG: JWT ohne Algorithmus-Verifizierung
import io.jsonwebtoken.Jwts;

public class VulnerableJwtVerifier {

    public Claims verifyToken(String token, String secret) {
        // Keine Algorithmus-Einschränkung - anfällig für Algorithmus-Verwirrung
        return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
    }
}

// ANFÄLLIG: Zertifikatsvalidierung deaktiviert
import javax.net.ssl.*;
import java.security.cert.X509Certificate;

public class InsecureSSL {

    public static void disableCertificateValidation() {
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) { }
                public void checkServerTrusted(X509Certificate[] certs, String authType) { }
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}
// ANFÄLLIG: JWT-Verifizierung mit Algorithmus-Verwirrung
const jwt = require('jsonwebtoken');

function verifyToken(token, secret) {
    // Kein Algorithmus spezifiziert - Angreifer kann "none" verwenden
    return jwt.verify(token, secret);
}

// ANFÄLLIG: Signatur-Verifizierung ignorieren
function parseJwtUnsafe(token) {
    const parts = token.split('.');
    const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
    // Signatur nicht verifiziert!
    return payload;
}

// ANFÄLLIG: Jeden Algorithmus akzeptieren
function verifyAnyAlgorithm(token, key) {
    const decoded = jwt.decode(token, { complete: true });
    // Algorithmus aus Token-Header verwenden - angreiferkontrolliert!
    return jwt.verify(token, key, { algorithms: [decoded.header.alg] });
}

Korrigierter Code

# SICHER: JWT-Verifizierung mit explizitem Algorithmus
import jwt
from jwt.exceptions import InvalidTokenError

ALLOWED_ALGORITHMS = ['RS256']  # Explizit nur RS256 erlauben

def verify_token_safe(token, public_key):
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=ALLOWED_ALGORITHMS,  # Explizite Algorithmus-Whitelist
            options={
                "verify_signature": True,
                "verify_exp": True,
                "verify_iat": True,
                "verify_nbf": True,
                "require": ["exp", "iat", "sub"]
            }
        )
        return payload
    except InvalidTokenError as e:
        raise ValueError(f"Ungültiges Token: {e}")

# SICHER: Vollständige JWT-Validierungsklasse
class SecureJwtValidator:
    def __init__(self, public_key, issuer, audience):
        self.public_key = public_key
        self.issuer = issuer
        self.audience = audience

    def validate(self, token):
        try:
            payload = jwt.decode(
                token,
                self.public_key,
                algorithms=['RS256'],  # Nur asymmetrisch
                issuer=self.issuer,
                audience=self.audience,
                options={
                    "verify_signature": True,
                    "verify_exp": True,
                    "verify_iat": True,
                    "verify_iss": True,
                    "verify_aud": True
                }
            )
            return payload
        except jwt.ExpiredSignatureError:
            raise ValueError("Token ist abgelaufen")
        except jwt.InvalidIssuerError:
            raise ValueError("Ungültiger Token-Aussteller")
        except jwt.InvalidAudienceError:
            raise ValueError("Ungültige Token-Zielgruppe")
        except jwt.InvalidSignatureError:
            raise ValueError("Ungültige Signatur")
        except Exception as e:
            raise ValueError(f"Token-Validierung fehlgeschlagen: {e}")

# SICHER: Signatur-Verifizierung für Dateien
import hashlib
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

def verify_file_signature(file_path, signature, public_key_pem):
    from cryptography.hazmat.backends import default_backend

    # Öffentlichen Schlüssel laden
    public_key = serialization.load_pem_public_key(
        public_key_pem.encode(),
        backend=default_backend()
    )

    # Dateiinhalt lesen
    with open(file_path, 'rb') as f:
        file_content = f.read()

    # Signatur verifizieren
    try:
        public_key.verify(
            signature,
            file_content,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except Exception:
        return False
// SICHER: JWT-Verifizierung mit Algorithmus-Einschränkung
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;

public class SecureJwtVerifier {

    private final Key publicKey;
    private final String expectedIssuer;
    private final String expectedAudience;

    public SecureJwtVerifier(Key publicKey, String issuer, String audience) {
        this.publicKey = publicKey;
        this.expectedIssuer = issuer;
        this.expectedAudience = audience;
    }

    public Claims verifyToken(String token) throws JwtException {
        JwtParser parser = Jwts.parserBuilder()
            .setSigningKey(publicKey)
            .requireIssuer(expectedIssuer)
            .requireAudience(expectedAudience)
            .build();

        Jws<Claims> jws = parser.parseClaimsJws(token);

        // Zusätzliche Algorithmus-Verifizierung
        String algorithm = jws.getHeader().getAlgorithm();
        if (!"RS256".equals(algorithm)) {
            throw new SignatureException("Ungültiger Algorithmus: " + algorithm);
        }

        return jws.getBody();
    }
}

// SICHER: Ordnungsgemäße Zertifikatsvalidierung
import javax.net.ssl.*;
import java.security.cert.*;

public class SecureSSLContext {

    public static SSLContext createSecureContext(String truststorePath, char[] password)
            throws Exception {

        // Truststore laden
        KeyStore trustStore = KeyStore.getInstance("JKS");
        try (FileInputStream fis = new FileInputStream(truststorePath)) {
            trustStore.load(fis, password);
        }

        // Trust-Manager mit Zertifikatsvalidierung erstellen
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);

        // SSL-Kontext erstellen
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());

        return sslContext;
    }

    // Benutzerdefinierter Zertifikatsvalidator mit zusätzlichen Prüfungen
    public static X509TrustManager createStrictTrustManager(KeyStore trustStore)
            throws Exception {

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);

        X509TrustManager defaultTm = (X509TrustManager) tmf.getTrustManagers()[0];

        return new X509TrustManager() {
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
                // Standard-Validierung
                defaultTm.checkServerTrusted(chain, authType);

                // Zusätzliche Prüfungen
                X509Certificate cert = chain[0];

                // Ablauf prüfen
                cert.checkValidity();

                // Schlüsselverwendung verifizieren
                boolean[] keyUsage = cert.getKeyUsage();
                if (keyUsage != null && !keyUsage[0]) {  // digitalSignature
                    throw new CertificateException("Ungültige Zertifikats-Schlüsselverwendung");
                }
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
                defaultTm.checkClientTrusted(chain, authType);
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return defaultTm.getAcceptedIssuers();
            }
        };
    }
}
// SICHER: JWT-Verifizierung mit expliziten Algorithmen
const jwt = require('jsonwebtoken');
const fs = require('fs');

class SecureJwtValidator {
    constructor(publicKey, options = {}) {
        this.publicKey = publicKey;
        this.issuer = options.issuer;
        this.audience = options.audience;
        this.allowedAlgorithms = ['RS256'];  // Nur asymmetrisch
    }

    verify(token) {
        try {
            const payload = jwt.verify(token, this.publicKey, {
                algorithms: this.allowedAlgorithms,  // Explizite Whitelist
                issuer: this.issuer,
                audience: this.audience,
                complete: false
            });

            return payload;
        } catch (error) {
            if (error instanceof jwt.TokenExpiredError) {
                throw new Error('Token ist abgelaufen');
            }
            if (error instanceof jwt.JsonWebTokenError) {
                throw new Error(`Ungültiges Token: ${error.message}`);
            }
            throw error;
        }
    }

    // Verifizieren mit zusätzlichen Header-Prüfungen
    verifyStrict(token) {
        // Zuerst ohne Verifizierung dekodieren um Header zu prüfen
        const decoded = jwt.decode(token, { complete: true });

        if (!decoded) {
            throw new Error('Ungültiges Token-Format');
        }

        // Algorithmus im Header verifizieren
        if (!this.allowedAlgorithms.includes(decoded.header.alg)) {
            throw new Error(`Algorithmus ${decoded.header.alg} nicht erlaubt`);
        }

        // Token-Typ verifizieren
        if (decoded.header.typ !== 'JWT') {
            throw new Error('Ungültiger Token-Typ');
        }

        // Jetzt Signatur verifizieren
        return this.verify(token);
    }
}

// SICHER: Datei-Signatur-Verifizierung
const crypto = require('crypto');

function verifyFileSignature(filePath, signature, publicKey) {
    const fileContent = fs.readFileSync(filePath);

    const verify = crypto.createVerify('RSA-SHA256');
    verify.update(fileContent);
    verify.end();

    const isValid = verify.verify(publicKey, signature, 'base64');

    if (!isValid) {
        throw new Error('Signatur-Verifizierung fehlgeschlagen');
    }

    return true;
}

// Verwendung
const publicKey = fs.readFileSync('public.pem');
const validator = new SecureJwtValidator(publicKey, {
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com'
});

try {
    const payload = validator.verifyStrict(token);
    console.log('Token gültig:', payload);
} catch (error) {
    console.error('Token ungültig:', error.message);
}

Ausgenutzt in der Praxis

Auth0 JWT-Bibliothek-Schwachstelle (2015)

Mehrere JWT-Bibliotheken wurden als anfällig für Algorithmus-Verwirrungsangriffe befunden, bei denen Angreifer RS256 auf HS256 ändern und Tokens mit dem öffentlichen Schlüssel signieren könnten, wodurch die Signatur-Verifizierung vollständig umgangen wurde.

CVE-2015-9235 - JWT "none"-Algorithmus

Bibliotheken, die den "none"-Algorithmus akzeptierten, ermöglichten Angreifern, unsignierte JWTs zu erstellen, die als gültig akzeptiert wurden, wodurch die Authentifizierung vollständig umgangen wurde.

Apple goto fail (2014)

Ein Codierfehler in Apples SSL-Implementierung führte dazu, dass die Signatur-Verifizierung übersprungen wurde, was Man-in-the-Middle-Angriffe auf iOS- und macOS-Geräte ermöglichte.


Tools zum Testen/Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-347: Improper Verification of Cryptographic Signature." https://cwe.mitre.org/data/definitions/347.html

  2. Auth0. "Critical Vulnerabilities in JSON Web Token Libraries." https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/