jwtdecoder.de

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.