JWT-Tokens sicher dekodieren
JWT (JSON Web Token) ist ein kompaktes, URL-sicheres Token-Format, das durch RFC 7519 definiert ist. Es wird für die Authentifizierung und den Informationsaustausch zwischen Parteien verwendet. JWTs sind zum De-facto-Standard für die sichere Darstellung von Ansprüchen (Claims) zwischen zwei Parteien geworden, beispielsweise zwischen einem Client und einem Server in einer modernen Webanwendung. Sie sind in sich geschlossen, das heißt, alle Informationen, die zur Authentifizierung einer Anfrage benötigt werden, sind direkt im Token selbst eingebettet, was die Notwendigkeit einer serverseitigen Sitzungsspeicherung in vielen Architekturen überflüssig macht.
Die Popularität von JWTs erstreckt sich über Single-Page-Anwendungen, mobile Apps, API-Gateways und die Mikroservices-Kommunikation. Wenn sich ein Benutzer bei einer Anwendung anmeldet, stellt der Server ein JWT aus, das der Client speichert und bei jeder nachfolgenden Anfrage vorlegt. Der Server überprüft dann die Signatur des Tokens, um die Anfrage zu authentifizieren und zu autorisieren. Zu verstehen, wie man JWTs sicher dekodiert und inspiziert, ist für Entwickler von entscheidender Bedeutung, die Authentifizierungsabläufe debuggen, Ansprüche überprüfen und tokenbezogene Probleme beheben müssen, ohne sensible Daten preiszugeben oder Schwachstellen einzuführen.
JWT-Struktur verstehen
Ein JWT besteht aus drei Teilen, die durch Punkte getrennt sind, wobei jeder Teil mit Base64URL (einer URL-sicheren Variante der Base64-Kodierung) kodiert ist:
header.payload.signature
Beispiel:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwi
bmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4
fwpMeJf36POk6yJV_adQssw5c
Das erste Segment ist der Header, der Metadaten über das Token enthält, einschließlich des Signaturalgorithmus und des Tokentyps. Das zweite Segment ist der Payload, der die Ansprüche oder Behauptungen enthält. Das dritte Segment ist die Signatur, die berechnet wird, indem der kodierte Header und Payload mit einem Punkt kombiniert und das Ergebnis mit dem im Header angegebenen Algorithmus zusammen mit einem geheimen Schlüssel oder privaten Schlüssel signiert wird.
Base64URL-Kodierung im Detail
JWT verwendet die Base64URL-Kodierung, die sich in zwei wichtigen Punkten von Standard-Base64 unterscheidet. Erstens ersetzt es das +-Zeichen durch -, um Probleme in URL-Abfrageparametern zu vermeiden, wo + als Leerzeichen interpretiert wird. Zweitens ersetzt es das /-Zeichen durch _, um Konflikte mit URL-Pfadtrennzeichen zu vermeiden. Darüber hinaus werden Auffüllzeichen (=) am Ende der JWT-Segmente entfernt, da sie für die Dekodierung unnötig sind und dem Token nur unnötige Bytes hinzufügen würden.
JWT-Header-Ansprüche
Der Header enthält typischerweise den Algorithmus und den Tokentyp, kann aber auch zusätzliche Metadaten für fortgeschrittene Szenarien enthalten.
| Anspruch | Name | Beispielwerte |
|---|---|---|
| alg | Algorithmus | HS256, RS256, ES256 |
| typ | Tokentyp | JWT |
| kid | Schlüssel-ID | unique-key-id |
| jku | JWK-Set-URL | https://example.com/.well-known/jwks.json |
Der kid-Anspruch (Key ID) ist besonders wichtig in Umgebungen, in denen mehrere Signaturschlüssel rotiert werden. Er ermöglicht es dem Server, den richtigen Schlüssel aus einem Schlüsselsatz nachzuschlagen, ohne raten zu müssen. Der jku-Anspruch verweist auf eine URL, die ein JSON Web Key Set (JWKS) enthält, das der Verifizierer abrufen kann, um den öffentlichen Schlüssel zur Signaturprüfung zu erhalten. Das Abrufen von Schlüsseln von URLs, die in nicht vertrauenswürdigen Token angegeben sind, birgt jedoch Sicherheitsrisiken und sollte nur mit ordnungsgemäßer Validierung erfolgen.
Häufige JWT-Payload-Ansprüche
Der Payload enthält Ansprüche, die Informationen über den Benutzer und das Token selbst bereitstellen. Diese Ansprüche werden in registrierte, öffentliche und private Ansprüche kategorisiert.
| Anspruch | Name | Beschreibung |
|---|---|---|
| sub | Subject | Benutzerkennung |
| iss | Issuer | Wer hat das Token ausgestellt |
| aud | Audience | Beabsichtigter Empfänger |
| exp | Expiration | Token-Ablaufzeitstempel |
| nbf | Not Before | Token gültig ab |
| iat | Issued At | Token-Erstellungszeit |
| jti | JWT ID | Eindeutige Token-Kennung |
Registrierte Ansprüche sind durch die JWT-Spezifikation standardisiert und haben spezifische Bedeutungen. Der exp-Anspruch ist besonders kritisch für die Sicherheit. Wenn ein Token keine Ablaufzeit enthält oder der Server diese nicht validiert, bleibt das Token auf unbestimmte Zeit gültig, was eine schwerwiegende Schwachstelle darstellt, falls das Token jemals preisgegeben wird. Der iat-Anspruch zeichnet auf, wann das Token erstellt wurde, und kann verwendet werden, um das Token-Alter zu bestimmen oder Token-Rotationsrichtlinien zu implementieren.
Über diese registrierten Ansprüche hinaus enthalten Anwendungen typischerweise benutzerdefinierte Ansprüche für Benutzerrollen, Berechtigungen, E-Mail-Adressen und andere anwendungsspezifische Daten. Ein Token könnte beispielsweise "role": "admin" oder "permissions": ["read", "write", "delete"] enthalten. Diese benutzerdefinierten Ansprüche sollten sorgfältig überprüft, aber ohne Signaturprüfung niemals vertraut werden.
Dekodieren vs. Verifizieren
Die Unterscheidung zwischen Dekodieren und Verifizieren ist das wichtigste Konzept, das man bei der Arbeit mit JWTs verstehen muss. Die Verwechslung der beiden ist eine häufige Quelle für Sicherheitslücken.
Dekodieren dekodiert lediglich Header und Payload mit Base64URL. Jeder kann ein JWT ohne geheimen Schlüssel dekodieren. Sie können jedes JWT dekodieren, indem Sie es in ein Dekodierwerkzeug einfügen oder eine einfache Base64URL-Dekodierfunktion auf den ersten beiden Segmenten ausführen. Dies macht JWTs nützlich für Inspektion und Debugging, bedeutet aber auch, dass die Payload-Inhalte öffentlich lesbar sind.
Verifizieren überprüft die Signatur mit einem geheimen Schlüssel (HMAC) oder öffentlichen Schlüssel (RSA/ECDSA). Dies bestätigt, dass das Token nicht manipuliert wurde und von einer vertrauenswürdigen Partei ausgestellt wurde. Die Verifizierung ist der Schritt, der Sicherheit bietet. Ohne sie könnte ein Angreifer den Payload ändern, um die Benutzer-ID oder Rolle zu verändern, den Header und Payload neu kodieren und eine neue Signatur erstellen, die für einen Server, der die Signaturprüfung überspringt oder falsch implementiert, gültig erscheint.
Algorithmus-Verwechslungsangriffe
Eine der gefährlichsten JWT-Schwachstellen ist der Algorithmus-Verwechslungsangriff, auch bekannt als Key-Confusion-Angriff. Dieser Angriff nutzt die Tatsache aus, dass der Algorithmus im Header angegeben ist, der angreiferkontrolliert ist, da der Header nur Base64URL-kodiert und nicht verschlüsselt ist.
In einem typischen Szenario verwendet ein Server RS256 (RSA mit SHA-256) zum Signieren von Token. Der öffentliche Schlüssel des Servers ist weit verbreitet, während der private Schlüssel geheim gehalten wird. Ein Angreifer fängt ein gültiges Token ab und ändert den alg-Header von RS256 auf HS256. Da HS256 einen symmetrischen geheimen Schlüssel verwendet, versucht der Server, die Signatur mit dem HS256-Algorithmus zu überprüfen. Der kritische Fehler ist, wenn der Server den RS256-öffentlichen Schlüssel als HS256-Geheimnis verwendet. Da der öffentliche Schlüssel öffentlich ist, kann der Angreifer eine gültige HS256-Signatur für jeden Payload unter Verwendung des öffentlichen Schlüssels als HMAC-Geheimnis berechnen. Der Server überprüft dann die Signatur mit demselben öffentlichen Schlüssel und akzeptiert das gefälschte Token.
Um diesen Angriff zu verhindern, müssen Server entweder vor der Verifizierung validieren, dass der Algorithmus mit dem erwarteten Algorithmus übereinstimmt, oder eine Bibliothek verwenden, die von Natur aus immun gegen diesen Angriff ist. Viele moderne JWT-Bibliotheken verwenden jetzt getrennte Methoden für asymmetrische und symmetrische Verifizierung, um dieses Risiko zu eliminieren.
Sichere Dekodierungsschritte
Befolgen Sie diese Schritte jedes Mal, wenn Sie ein JWT-Token dekodieren und inspizieren müssen.
-
Kopieren Sie das JWT-Token aus Ihrer Anwendung. Stellen Sie sicher, dass Sie das vollständige Token einschließlich aller drei durch Punkte getrennten Segmente erfasst haben. Ein unvollständiges Token kann nicht richtig dekodiert werden.
-
Verwenden Sie ein vertrauenswürdiges Dekodierwerkzeug wie den JWT Decoder, der vollständig im Browser oder auf Ihrem lokalen Rechner läuft. Vermeiden Sie es, Token in unbekannte Online-Tools einzufügen, die Ihre Token protokollieren oder an entfernte Server übertragen könnten. Selbst wenn ein Token kurzlebig ist, könnte die Offenlegung gegenüber Dritten es ihnen ermöglichen, es innerhalb seines Gültigkeitsfensters erneut zu verwenden.
-
Überprüfen Sie den Header auf den Algorithmus. Bestätigen Sie, dass der Algorithmus dem entspricht, was Sie von Ihrem Server erwarten. Wenn Sie einen unerwarteten Algorithmus wie
noneoder eine Änderung vonRS256aufHS256sehen, behandeln Sie das Token als verdächtig. -
Überprüfen Sie die Payload-Ansprüche. Prüfen Sie die Zeitstempel
exp(Ablauf) undnbf(Not Before), um zu bestätigen, dass das Token aktuell gültig ist. Verifizieren Sie, dass deriss(Issuer) mit der Kennung Ihres Servers übereinstimmt. Prüfen Sie, ob deraud(Audience)-Anspruch, falls vorhanden, Ihre Anwendung einschließt. -
Geben Sie das Token niemals öffentlich weiter. Token, die in Fehlerberichten, Stack Overflow-Fragen oder GitHub-Gists gepostet werden, können von jedem missbraucht werden, der sie vor ihrem Ablauf findet. Schwärzen oder ersetzen Sie Token immer durch Dummy-Beispiele, bevor Sie sie teilen.
-
Verifizieren Sie niemals mit nicht vertrauenswürdigen Schlüsseln. Wenn Ihr Verifizierungscode Schlüssel von Benutzereingaben, entfernten URLs oder Konfigurationsdateien akzeptiert, die von Angreifern geändert werden können, ist Ihr gesamtes Authentifizierungssystem kompromittiert.
JWTs im Code dekodieren
Während Online-Tools für die Ad-hoc-Inspektion praktisch sind, müssen Sie JWTs oft programmatisch in Ihrer Anwendung dekodieren. Hier ist, wie Sie einen JWT-Payload in einigen gängigen Programmiersprachen dekodieren, ohne eine Signaturprüfung durchzuführen.
JavaScript (Node.js)
function decodeJWT(token) {
const payload = token.split('.')[1];
const decoded = Buffer.from(payload, 'base64url').toString('utf8');
return JSON.parse(decoded);
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
console.log(decodeJWT(token));
Python
import base64
import json
def decode_jwt(token):
payload = token.split('.')[1]
# Add padding for base64 decoding
padding = 4 - len(payload) % 4
if padding != 4:
payload += '=' * padding
decoded = base64.urlsafe_b64decode(payload)
return json.loads(decoded)
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(decode_jwt(token))
Diese Beispiele demonstrieren die Dekodierung nur zu Inspektionszwecken. Verwenden Sie für die Produktionsverifizierung immer eine gut geprüfte JWT-Bibliothek, die Algorithmusvalidierung, Schlüsselverwaltung und Signaturprüfung korrekt handhabt.
Sicherheits-Best-Practices
Über das sichere Dekodieren hinaus halten die folgenden Best Practices Ihre JWT-Implementierung sicher.
Verwenden Sie kurze Ablaufzeiten. Zugriffstoken sollten innerhalb von 15 bis 60 Minuten ablaufen. Kurzlebige Token begrenzen den Schaden, falls ein Token preisgegeben wird. Kombinieren Sie Zugriffstoken mit Aktualisierungstoken (Refresh Tokens), die längere Laufzeiten haben und einzeln widerrufen werden können.
Validieren Sie alle erforderlichen Ansprüche. Überprüfen Sie während der Verifizierung immer die Ansprüche exp, nbf, iss und aud. Viele Angriffe nutzen fehlende oder falsche Anspruchsvalidierung aus.
Verwenden Sie asymmetrische Algorithmen für verteilte Systeme. RS256 oder ES256 ermöglichen es dem Server, Token mit einem privaten Schlüssel zu signieren, während jeder Dienst sie mit dem öffentlichen Schlüssel verifizieren kann. Dies macht die gemeinsame Nutzung von Geheimnissen zwischen Diensten überflüssig.
Implementieren Sie eine Token-Sperrliste. Für Szenarien, in denen Sie Token vor ihrem Ablauf widerrufen müssen, führen Sie eine Sperrliste oder eine Token-Versionsnummer, die in Ihrer Datenbank gespeichert ist. Erhöhen Sie die Version bei Passwortänderungen oder Kontosperrungen, um alle vorhandenen Token zu ungültig zu machen.
Sicherheitswarnung
Akzeptieren Sie niemals Token von nicht vertrauenswürdigen Quellen. Verifizieren Sie immer die Signatur auf Ihrem Server, bevor Sie dem Payload vertrauen. Achten Sie auf Algorithmus-Verwechslungsangriffe, bei denen ein Angreifer RS256 auf HS256 ändert, um den öffentlichen Schlüssel als Geheimnis zu verwenden. Verwenden Sie seriöse JWT-Bibliotheken, die die Signaturprüfung sicher implementieren, und halten Sie sie auf dem neuesten Stand, um von den aktuellen Sicherheitspatches zu profitieren.
Denken Sie daran: Dekodieren ist nicht Verifizieren. Jeder kann den Inhalt eines JWT lesen, indem er einfach den Payload Base64-dekodiert. Behandeln Sie JWT-Payloads in Bezug auf Vertraulichkeit als öffentliche Informationen und verlassen Sie sich für Integrität und Authentizität auf die Signaturprüfung.