JWT Webtokens
Bron: PortSwigger Web Security Academy Auteur: Johan Beysen | Fox & Fish Cybersecurity
1. Wat is een JWT Token?
Een JWT (JSON Web Token) is een digitaal paspoort dat de server aanmaakt wanneer je inlogt. Het bevat wie je bent en wat je mag doen, en is cryptografisch ondertekend tegen vervalsing.
Klassieke Sessie vs JWT
| Klassieke sessie | JWT | |
|---|---|---|
| Server onthoudt | Ja — sessiestatus opgeslagen | Nee — stateless |
| Client ontvangt | Enkel een sessiecode | Het volledige token met data |
| Verificatie | Server raadpleegt sessiestore | Server verifieert handtekening |
| Schaalbaarheid | Alle servers moeten sessie kennen | Elke server kan zelfstandig valideren |
Gebruik case
Stel je hebt 5 verschillende servers. Bij een klassieke sessie moet elke server weten wie je bent. Met JWT stuur je gewoon je token mee — elke server verifieert de handtekening zelfstandig.
2. Structuur van een JWT
Een JWT bestaat uit drie delen, gescheiden door een punt: header.payload.signature
| Deel | Beschrijving |
|---|---|
| Header | Welk algoritme gebruikt voor de handtekening |
| Payload | Jouw gegevens: naam, rol, rechten, ... |
| Signature | Cryptografische handtekening die alles beschermt |
Header en payload zijn gewoon base64url-encoded JSON — leesbaar via jwt.io of Burp Decoder. Daarom is de cryptografische handtekening cruciaal.
JWS vs JWE
| Variant | Payload | Beschrijving |
|---|---|---|
| JWT | — | Concept/envelop |
| JWS (JSON Web Signature) | Leesbaar (base64) | Ondertekend zodat niemand het kan aanpassen |
| JWE (JSON Web Encryption) | Volledig versleuteld | Niemand kan lezen zonder de sleutel |
JWS is wat iedereen bedoelt als ze "JWT" zeggen. Structuur: header.payload.signature
3. JWT Signature
De server neemt de header + payload, gooit die door een hashfunctie samen met een geheime sleutel, en dat resultaat is de signature.
Eén byte veranderen = signature ongeldig = token geweigerd
Kwetsbaarheid
In principe kom je nergens zonder de geheime sleutel. Je kan de header en payload lezen, maar geen nieuwe geldige tokens aanmaken.
4. JWT Aanvallen
4.1 Probleem 1 — Signature wordt niet gecontroleerd
De developer gebruikt decode() in plaats van verify():
| Functie | Gedrag |
|---|---|
verify() |
Decodeert én controleert de signature |
decode() |
Decodeert alleen, geen controle |
Als decode() gebruikt wordt, accepteert de server aangepaste tokens zonder handtekeningcontrole.
Aanval: Pas de payload aan (bijv. "role": "admin") en stuur het token terug. De server accepteert het gewoon.
4.2 Probleem 2 — Zwakke of Uitgelekte Geheime Sleutel
Als de geheime sleutel:
- Te zwak is → bruteforceable
- Per ongeluk in GitHub staat → uitgelekt
- Een standaardwaarde is zoals
secretofpassword→ raadbaar
Dan kan een aanvaller zelf geldige tokens aanmaken voor om het even welke gebruiker of rol.
4.3 Brute Force van de Geheime Sleutel
hashcat -a 0 -m 16500 <jwt> <wordlist>
Wordlist samenstellen
Voeg alle gekende wachtwoorden (van dev team of bedrijf) samen in een wordlist. Gebruik ook standaard JWT-wordlists beschikbaar op GitHub.
5. Algorithm Confusion — alg: none
De header van een JWT bevat het algoritme:
{ "alg": "HS256", "typ": "JWT" }
Pas dit aan naar "alg": "none" — dit betekent: geen signature nodig.
Sommige servers weigeren dit, maar je kan de filter omzeilen met varianten zoals:
- None
- NONE
- nOnE
Als de server niet goed filtert, accepteert hij een token zonder enige handtekening.
# Token met alg: none heeft geen signature
header.payload. ← lege signature
6. Header Parameters Misbruiken
Naast alg kunnen JWT-headers ook bevatten:
| Parameter | Beschrijving |
|---|---|
jwk |
Embedded JSON object met de sleutel |
jku |
URL van waaruit de server sleutels ophaalt |
kid |
ID van de sleutel die de server moet gebruiken |
Hiermee vertel je de server welke sleutel hij moet gebruiken om de signature te verifiëren.
6.1 Self-Signed JWT via jwk Parameter
Idealiter gebruikt de server enkel een whitelist van vertrouwde public keys. Bij foute configuratie gebruikt de server eender welke key embedded in de jwk parameter.
Aanval met Burp JWT Editor extensie:
- Genereer een nieuw RSA key pair in JWT Editor
- Verander de payload (
"role": "admin") - Klik "Attack" → "Embedded JWK"
- De extensie voegt je public key in als
jwken tekent met je private key - Server verifieert met de embedded key → aanval slaagt
6.2 Self-Signed JWT via jku Parameter
JWK Sets worden soms publiek beschikbaar gesteld via /.well-known/jwks.json.
Aanval:
- Host een eigen JWKS op een aanvaller-gecontroleerde server
- Verander
jkuin de header naar jouw URL - Server haalt jouw JWKS op en verifieert met jouw public key
- Jij hebt getekend met de bijhorende private key → aanval slaagt
Whitelist bypass
Meer beveiligde servers fetchen enkel keys van vertrouwde domeinen. Probeer URL parsing discrepancies zoals SSRF bypass-technieken.
6.3 Self-Signed JWT via kid Parameter
kid = ID van de sleutel, opgeslagen op de server. De waarde komt uit het token zelf — dus van de client.
Aanval — Path Traversal via kid:
{ "kid": "../../dev/null" }
- Server zoekt dat "bestand" op →
/dev/null= leeg bestand = lege string - Jij signt de token met een lege string als geheim
- Server verifieert met diezelfde lege string → match!
- Jij hebt een zelfgemaakt token dat de server volledig vertrouwt
7. Algorithm Confusion Attack (RS256 → HS256)
Bij asymmetrische algoritmen (RS256):
- Server tekent met private key
- Server verifieert met public key
Aanval: Verander het algoritme van RS256 naar HS256. De server denkt nu dat HS256 gebruikt wordt en verifieert met zijn public key als HMAC-secret. Jij tekent het token ook met diezelfde public key.
Public Key Extracten
Als de public key niet gekend is:
# Via docker container
docker run --rm -it portswigger/sig2n <token1> <token2>
# Via Python tool
python3 jwt_forgery.py <token1> <token2>
Aanvalsplan bij RS256 Token
{
"alg": "RS256",
"kid": "F0EBED7A...",
"x5t": "8OvteoSv...",
"typ": "at+jwt"
}
| Parameter | Analyse |
|---|---|
alg: RS256 |
Asymmetrisch → kwetsbaar voor algorithm confusion |
kid eindigt op RS256 |
Test of een key met ...HS256 bestaat |
x5t |
X.509 certificate thumbprint → public key zit in certificaat |
typ: at+jwt |
OAuth2 Access Token → Bearer token in Authorization header |
Stappen:
- Zoek public key via
/jwks.jsonof extraheer viax5t - Probeer algorithm confusion: verander RS256 → HS256
- Sign het token met de public key als HMAC-secret
- Controleer of
kidkwetsbaar is voor directory traversal
Fox & Fish Cybersecurity | Intern gebruik