Case-Study · Internes ERP-System (Java + Spring Boot, alte jose4j-Version)
Case-Study: alg=none-Schwachstelle in legacy-System
Legacy-Auth-Microservice mit veralteter JWT-Library: alg-Whitelisting fehlte. Angreifer konnte beliebige User-IDs unterschieben.
Eckdaten
Schwachstelle
alg=none akzeptiert
Schweregrad
Critical (CVSS 9.8)
Discovery
Externer Pentest 2024-Q3
Time to Fix
4 Stunden nach Disclosure
Schritte / Maßnahmen
- ✓ jose4j-Version 0.7.x hatte default-permissive Config
- ✓ Verifier akzeptierte UnsecuredJWS (alg=none) als gültig
- ✓ Angreifer baute Token mit sub=admin, alg=none, leerer Signatur
- ✓ Fix: AlgorithmConstraints auf Whitelist HS256/RS256 setzen
- ✓ jose4j-Update auf 0.9.x mit secure-by-default
Ein deutsches Mittelstands-Unternehmen betreibt ein internes ERP-System für ~200 Mitarbeiter. Auth läuft über JWT, ausgestellt vom hauseigenen Identity-Provider. Externer Pentest 2024-Q3 deckte eine kritische Schwachstelle auf: das System akzeptierte JWTs mit alg=none als gültig - ohne Signaturprüfung.
Wie der Angriff lief
Pentester erstellte ein Token manuell:
// Header
{ "alg": "none", "typ": "JWT" }
// Base64url: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
// Payload
{ "sub": "admin", "role": "superuser", "exp": 9999999999 }
// Base64url: eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJzdXBlcnVzZXIiLCJleHAiOjk5OTk5OTk5OTl9
// Signature: leer
// Vollständiges Token: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbi...<leer>
Das System akzeptierte dieses Token. Pentester hatte effektiv Admin-Rechte ohne irgendeinen Schlüssel zu kennen.
Root-Cause-Analyse
Auth-Code (Java + jose4j 0.7.x):
JwtConsumer consumer = new JwtConsumerBuilder()
.setVerificationKey(publicKey)
.setExpectedIssuer("internal-idp")
.setExpectedAudience("erp-system")
.build();
JwtClaims claims = consumer.processToClaims(token);
Problem: keine AlgorithmConstraints gesetzt. jose4j 0.7.x hatte einen permissiveren Default als spätere Versionen - alg=none-Tokens wurden trotz gesetztem verification key akzeptiert, weil der Verifier den alg-Wert respektierte und "none" als gültig interpretierte.
Der Fix
JwtConsumer consumer = new JwtConsumerBuilder()
.setVerificationKey(publicKey)
.setExpectedIssuer("internal-idp")
.setExpectedAudience("erp-system")
// EXPLIZITE Algorithm-Whitelist
.setJwsAlgorithmConstraints(
new AlgorithmConstraints(
AlgorithmConstraints.ConstraintType.PERMIT,
AlgorithmIdentifiers.HMAC_SHA256,
AlgorithmIdentifiers.RSA_USING_SHA256
)
)
.build();
Plus: jose4j-Update auf 0.9.x (secure-by-default), automated Test der alg=none-Variante in CI.
Lessons learned
- Library-Defaults niemals vertrauen - explizite Whitelists sind Pflicht
- Dependency-Updates auf JWT-Libraries hoch priorisieren - typisch sind Security-Patches
- Negative Tests: nicht nur "gültiges Token wird akzeptiert" testen, sondern auch "alg=none wird abgelehnt", "alg=HS256-Token mit Public Key als Secret wird abgelehnt" (RSA-zu-HMAC-Verwechslung)
- Pentest-Rhythmus: extern, nicht nur intern - interne Reviews finden solche Schwachstellen oft nicht, weil "wir haben ja eine Auth-Library"
Verwandte Schwachstelle: RSA-zu-HMAC-Verwechslung
Wenn Verifier RSA erwartet (alg=RS256), aber alg-Header nicht prüft: Angreifer kennt den Public Key (z.B. aus JWKS). Setzt alg=HS256, signiert das Token mit dem Public Key als HMAC-Secret. Verifier benutzt den Public Key als HMAC-Verification-Key - Signatur passt. Fix: nicht nur alg whitelisten, sondern auch Key-Type prüfen.