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
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Datenmanipulation Unsignierte oder unsachgemäß verifizierte Daten können von Angreifern ohne Erkennung modifiziert werden. |
| Authentifizierung | Umfang: Authentifizierungs-Bypass Gefälschte Signaturen ermöglichen Angreifern, legitime Benutzer oder Systeme zu imitieren. |
| Zugriffskontrolle | Umfang: 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
-
jwt_tool — JWT-Ausnutzungs-Toolkit.
-
Burp Suite JWT Extensions — JWT-Manipulation und -Tests.
-
jwt.io — JWT-Decoder und Debugger.
-
SSLyze — SSL/TLS-Konfigurations-Analyzer.
CVE-Beispiele
-
CVE-2015-9235 — JWT "none"-Algorithmus-Schwachstelle.
-
CVE-2016-10555 — node-jose Algorithmus-Verwirrung.
-
CVE-2014-1266 — Apple goto fail SSL-Schwachstelle.
Referenzen
-
MITRE. "CWE-347: Improper Verification of Cryptographic Signature." https://cwe.mitre.org/data/definitions/347.html
-
Auth0. "Critical Vulnerabilities in JSON Web Token Libraries." https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/