cyber / cybersecurite-fondamentale
Attaques sur les JWT et défenses
Explication
Ce que vous allez apprendre
- Comprendre pourquoi un JWT n'exige aucun état côté serveur, et ce que cela implique pour sa vérification
- Reconnaître l'attaque par confusion d'algorithme et savoir pourquoi il faut toujours imposer l'algorithme attendu
- Vérifier systématiquement
exp,iat,audetiss, pas seulement la signature du jeton - Comprendre pourquoi révoquer un JWT stateless est structurellement difficile
- Mettre en place une liste noire de jetons révoqués comme compromis pratique
Dans quel contexte ?
Une API accepte un JWT signé en RS256, où la clé publique de vérification est largement diffusée (c'est son rôle). Un attaquant modifie l'en-tête du jeton pour annoncer l'algorithme HS256 au lieu de RS256, puis signe son faux jeton en utilisant la clé PUBLIQUE comme clé secrète HMAC. Si le code de vérification fait aveuglément confiance à l'algorithme annoncé dans le jeton plutôt que d'imposer RS256 explicitement, ce jeton forgé est accepté comme valide.
Le JWT, un jeton qui se fait confiance à lui-même
Un JWT (JSON Web Token) est particulier : contrairement à une session classique stockée côté serveur, il contient lui-même toutes les informations nécessaires (identité, rôle, expiration) et une signature qui prouve qu'il n'a pas été modifié depuis son émission. Le serveur n'a donc pas besoin de conserver un état — mais cette simplicité crée aussi des pièges spécifiques si la vérification n'est pas rigoureuse.
L'attaque par confusion d'algorithme, le piège le plus célèbre
Piège fréquent
Un JWT annonce lui-même, dans son en-tête, quel algorithme de signature a été utilisé. Si le code de vérification fait aveuglément confiance à cette annonce plutôt que d'imposer explicitement l'algorithme attendu, un attaquant peut fabriquer un jeton qui prétend utiliser un algorithme différent (par exemple HS256 au lieu de RS256) pour tromper la vérification. La leçon à en tirer : ne jamais laisser le jeton dicter comment il doit être vérifié.
Les champs de validité qu'on oublie souvent
Vérifier uniquement la signature ne suffit pas.
| Champ | Question posée | Risque si non vérifié |
|---|---|---|
exp | Le jeton est-il expiré ? | Un jeton volé reste valide indéfiniment |
iat | Le jeton est-il anormalement ancien ? | Rejeu d'un jeton émis il y a longtemps |
aud | Le jeton est-il destiné à CE service ? | Rejeu d'un jeton légitime sur un autre service |
iss | Le jeton vient-il bien de l'émetteur attendu ? | Acceptation d'un jeton forgé par un tiers |
Le problème de la révocation, inhérent au modèle stateless
Bonne pratique
Comme le JWT ne dépend d'aucun état côté serveur, il reste valide jusqu'à son expiration naturelle même si le compte est compromis entre-temps. Maintenez une liste noire (denylist) de jetons révoqués, vérifiée en plus de la signature — un compromis qui réintroduit un peu d'état, mais uniquement pour les cas de révocation exceptionnelle.
Commandes & code
Attaques sur les JWT et défenses
Un JWT mal validé est une porte d'entrée directe vers l'usurpation d'identité. Les attaques ciblent presque toujours la phase de vérification, pas le contenu.
# Attaque "alg=none" : certaines libs (mal configurées) acceptent un token sans signature du tout
import base64, json
header = base64.urlsafe_b64encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()).rstrip(b"=")
payload = base64.urlsafe_b64encode(json.dumps({"sub": "admin", "role": "admin"}).encode()).rstrip(b"=")
forged_token = header + b"." + payload + b"." # signature vide, mais "alg: none" dit de ne pas vérifier
# Défense : whitelister explicitement les algorithmes acceptés, JAMAIS déduire l'algo du header du token
import jwt
try:
jwt.decode(forged_token, key="secret", algorithms=["HS256"]) # rejette "none" car pas dans la whitelist
except jwt.InvalidAlgorithmError:
print("Token rejeté : algorithme non autorisé")# Attaque de confusion d'algorithme RS256 -> HS256
# Si le serveur vérifie avec HS256 en utilisant la CLÉ PUBLIQUE RS256 comme clé secrète HMAC,
# un attaquant qui connaît la clé publique (souvent publique !) peut forger un token valide
import jwt
public_key = open("public_key.pem", "rb").read() # la clé publique RSA, souvent distribuée librement
# Un attaquant signe son propre payload en HMAC-SHA256 EN UTILISANT la clé publique comme secret
forged = jwt.encode({"sub": "admin"}, public_key, algorithm="HS256")
# Défense : ne JAMAIS accepter plusieurs familles d'algorithmes pour un même endpoint de vérification
jwt.decode(token, key=public_key, algorithms=["RS256"]) # HS256 explicitement exclu de la liste# Validation incomplète : oublier exp, iss, aud laisse la porte ouverte au replay et à la confusion de service
import jwt
from datetime import datetime, timezone
def verify_token_strict(token: str) -> dict:
payload = jwt.decode(
token,
key=PUBLIC_KEY,
algorithms=["RS256"],
audience="api.technologik.fr", # empêche un token émis pour un AUTRE service d'être réutilisé ici
issuer="https://auth.technologik.fr",
options={
"require": ["exp", "iat", "sub", "aud", "iss"], # rejette tout token où un claim obligatoire manque
},
)
return payload
# Révocation : un JWT est stateless par nature -> maintenir une denylist (Redis, TTL = exp restant) pour le logout
import redis
r = redis.Redis()
def revoke_token(jti: str, ttl_seconds: int):
r.setex(f"revoked:{jti}", ttl_seconds, "1")
def is_revoked(jti: str) -> bool:
return r.exists(f"revoked:{jti}") == 1# Attaque par clé faible / brute-force hors ligne sur un secret HS256 court
# jwt_tool ou hashcat peuvent casser un secret HMAC faible en local, hors ligne, sans toucher le serveur
# hashcat -m 16500 token.txt wordlist.txt
# Défense : secret HS256 fort ET rotation, ou mieux, migrer vers RS256/ES256 (clé privée jamais partagée)
import secrets
strong_secret = secrets.token_urlsafe(64) # 512 bits d'entropie, jamais un mot de passe humainRésumé
- Toujours whitelister explicitement les algorithmes acceptés à la vérification, jamais les déduire du token.
- La confusion RS256/HS256 permet de forger des tokens si la clé publique sert de secret HMAC : séparer strictement les familles d'algorithmes par endpoint.
- Valider systématiquement
exp,iat,aud,issen plus de la signature. - Un JWT est stateless : prévoir une denylist de révocation pour le logout et la compromission de compte.
Exercices pratiques
Mission : le jeton admin forgé avec la clé publique
Objectif : Comprendre une attaque de confusion d'algorithme sur JWT et corriger la vérification pour qu'elle résiste à un jeton forgé.
Contexte
L'API de Technologik vérifie ses JWT signés en RS256 avec ce code :
payload = jwt.decode(token, key=public_or_secret_key, algorithms=jwt.get_unverified_header(token)["alg"])La clé publique RSA utilisée pour vérifier la signature est distribuée librement sur un endpoint /.well-known/jwks.json, comme c'est l'usage normal pour RS256. Un chercheur en sécurité signale pouvoir se connecter en tant qu'administrateur sans connaître aucun mot de passe.