Ratgeber · JWT 2026
alg=none - wie Libraries Token einfach akzeptierten
Was 2015 mehrere Libraries gleichzeitig hatten: Algorithm-Verwechslung. Wie man das heute zuverlässig verhindert (Algorithm-Whitelisting).
Eine der berühmtesten JWT-Schwachstellen
2015 deckte Tim McLean (Auth0) eine kritische Schwachstelle in mehreren JWT-Libraries auf: sie akzeptierten Tokens mit Header {"alg":"none"} als gültig - ohne Signaturprüfung. Angreifer konnte Tokens mit beliebigen Claims bauen und Server akzeptierten sie.
Wie der Angriff funktioniert
Standard-JWT mit HS256: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSJ9.SIGNATURE
Angreifer-modifiziertes JWT mit alg=none:
// Header: { "alg": "none", "typ": "JWT" }
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
// Payload: { "sub": "admin" }
.eyJzdWIiOiJhZG1pbiJ9
// Signature: leer
.
// Vollständiges Token:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiJ9.
Naive Verifier-Code:
function verifyToken(token, secret) {
const [headerB64, payloadB64, sigB64] = token.split('.');
const header = JSON.parse(atob(headerB64));
// BUG: alg=none wird hier "respektiert"
if (header.alg === 'none') {
return JSON.parse(atob(payloadB64)); // accept!
}
// sonst HMAC prüfen...
}
Server gibt Admin-Rechte. Angreifer hat das System ohne Schlüssel kompromittiert.
Warum das Pattern in Libraries vorkam
RFC 7519 erlaubt unsigned JWTs (alg=none) für Use-Cases ohne Signaturanforderung. Library-Autoren wollten konform sein und unterstützten die Variante. Defaults waren oft "alle Algorithmen erlauben", nicht "explicit opt-in".
Die 2015er Welle traf: PyJWT, jose4j, ruby-jwt, node-jsonwebtoken - alle in verschiedenen Versionen. Alle haben das gepatched.
Der Fix: Algorithm-Whitelist
Der Verifier muss eine explizite Liste erlaubter Algorithmen führen und alles andere ablehnen. Beispiele:
// PyJWT (modern)
jwt.decode(token, key, algorithms=['HS256']) // alles andere wird abgelehnt
// node-jsonwebtoken
jwt.verify(token, secret, { algorithms: ['HS256'] })
// Java/jose4j
new JwtConsumerBuilder()
.setVerificationKey(key)
.setJwsAlgorithmConstraints(
AlgorithmConstraints.ConstraintType.PERMIT,
AlgorithmIdentifiers.HMAC_SHA256
)
.build();
Wenn alg-Whitelist gesetzt ist UND "none" nicht drauf steht → Library lehnt Token mit alg=none ab, bevor Signatur überhaupt geprüft wird.
Verwandte Schwachstelle: Algorithm-Confusion
Eine subtilere Variante: Verifier erwartet RS256 (asymmetrisch). Angreifer ändert alg auf HS256 (symmetrisch) und signiert das Token mit dem Public Key (öffentlich bekannt) als HMAC-Secret.
Wenn die Library den Verification-Key blind als HMAC-Secret durchreicht, stimmt die Signatur - und Angreifer hat gültiges Token, weil er den Public Key kennt.
Fix: nicht nur alg whitelisten, sondern auch Key-Type prüfen. Bei vielen Libraries muss man explizit zwei Code-Pfade für HMAC vs Asymmetrisch trennen.
Wie verifiziert man heute, dass man sicher ist?
- Library-Version aktuell halten: alle modernen Versionen (PyJWT ≥ 2.0, node-jsonwebtoken ≥ 9.0, jose4j ≥ 0.9) haben sichere Defaults.
- algorithms-Parameter explizit setzen: nie auf Defaults vertrauen.
- Negative Tests in CI: einen Test, der ein alg=none-Token gegen den Verifier wirft und assertet, dass es abgelehnt wird.
- Pentest: extern, alle 6-12 Monate. Wäre die "Internal-Audit"-Sicht oft nicht aufgefallen.
Negative-Test-Code (Beispiel Node.js)
test('rejects alg=none', () => {
const evilToken = createUnsecuredJWT({ sub: 'admin' });
expect(() =>
jwt.verify(evilToken, secret, { algorithms: ['HS256'] })
).toThrow();
});
test('rejects RS256 token with HMAC-substituted alg', () => {
// Build a token: alg=HS256, signed with publicKey as HMAC-secret
const tokenForged = createHMACSignedToken(publicKey, { sub: 'admin' });
expect(() =>
jwt.verify(tokenForged, publicKey, { algorithms: ['RS256'] })
).toThrow();
});
Real-World-Case
Siehe Case-Study alg=none Sicherheitslücke aufgedeckt - Pentest aus 2024 bei einem deutschen Mittelstands-Unternehmen, das eine veraltete jose4j-Version benutzte.
Im JWT Decoder erkennt das Tool alg=none-Tokens und zeigt einen entsprechenden Warning - der Decode funktioniert (weil das Format trivial ist), aber als "nicht verifizierbar" markiert.