Neutron, notre moteur d’IA, a obtenu un score de 97,1 % sur le benchmark CyberGym de l’UC Berkeley. En savoir plus

Analyse approfondie

Analyse approfondie

Falsification de JWT dans python-jose avec une clé HMAC vide (CVE-2026-120459)

python-jose jusqu'à 3.5.0 accepte un secret HMAC vide : un JWT forgé est validé lorsque la clé de l'application est vide. Aucune version corrigée.

En bref

  • python-jose accepte une clé HMAC de longueur nulle. Lorsque la clé de vérification HS256, HS384 ou HS512 d'une application est vide, un attaquant peut calculer la signature avec cette clé vide et faire accepter des revendications forgées.
  • Toutes les versions jusqu'à 3.5.0 sont concernées. La dernière version reste 3.5.0, publiée le 2025-05-28, et aucune version corrigée n'existe au 2026-11-09.
  • L'enregistrement CVE la note CVSS v4.0 9,1 Critique et CVSS v3.1 7,4 Élevée. La preuve de concept du rapporteur est publique dans le ticket #398 ; aucune exploitation n'est connue, et le CVE n'est pas dans le catalogue KEV de la CISA au 2026-11-09.
  • Rejetez les clés vides dans votre propre code ou passez à une bibliothèque qui le fait. PyJWT 2.15.1 refuse une clé HMAC vide avec InvalidKeyError.

Suis-je concerné ?

Toutes ces conditions doivent être réunies :

  • Vous vérifiez des jetons JSON Web avec python-jose version 3.5.0 ou antérieure, en HS256, HS384 ou HS512.
  • La clé de vérification peut prendre une valeur vide : chaîne vide, octets vides, ou JWK oct dont le membre k est vide.
  • Votre application fait confiance aux revendications d'un jeton qu'un attaquant peut fournir.

Un secret fixe et non vide n'est pas exploitable. La faille compte lorsque la clé est assemblée à l'exécution. Les exemples sont une variable d'environnement avec une valeur par défaut vide, un point de terminaison JWKS qui renvoie un k vide, ou une colonne de base de données jamais renseignée.

Le ticket du rapporteur note aussi que jwt.decode() met algorithms à None par défaut, ce qui contourne la liste blanche d'algorithmes. C'est une faiblesse distincte, pas la faille de clé vide, mais une application peut combiner les deux et laisser l'attaquant choisir l'algorithme.

Bibliothèque Versions concernées Version corrigée
python-jose 0 à 3.5.0 Aucune au 2026-11-09

Vérifiez ce qui est installé :

pip show python-jose
pip freeze | grep -i jose
grep -rn "python-jose" requirements*.txt pyproject.toml Pipfile.lock poetry.lock 2>/dev/null

Cherchez ensuite chaque appel de vérification et la source de sa clé :

grep -rn "jwt.decode\|jws.verify" --include="*.py" .

Comment corriger ?

Aucune version corrigée de python-jose n'existe. La correction pratique consiste à ne plus transmettre de clé vide à la bibliothèque, et à migrer vers une bibliothèque maintenue qui la refuse.

  1. Ajoutez un contrôle à chaque site de vérification pour rejeter une chaîne vide, des octets vides ou un JWK oct dont le k est vide avant que jwt.decode() ne s'exécute :
# Reject empty HMAC key material before python-jose sees it.
from jose.exceptions import JWKError

def require_nonempty_hmac_key(key):
    if isinstance(key, (str, bytes)) and len(key) == 0:
        raise JWKError("HMAC key must not be empty")
    if isinstance(key, dict) and key.get("kty") == "oct":
        if not key.get("k"):
            raise JWKError("oct JWK k must not be empty")
    return key
  1. Passez une liste blanche d'algorithmes explicite à chaque appel, même si ce n'est pas la faille de clé vide elle-même :
claims = jwt.decode(token, require_nonempty_hmac_key(key), algorithms=["HS256"])
  1. Migrez vers PyJWT, qui refuse la clé vide lors de la préparation. Le contrôle se trouve dans HMACAlgorithm.prepare_key et lève InvalidKeyError("HMAC key must not be empty.").
import jwt as pyjwt

claims = pyjwt.decode(token, key, algorithms=["HS256"])
  1. Si la clé peut venir d'un document JWKS, validez le document au moment de la récupération et rejetez toute clé oct avec un k vide. N'attendez pas l'échec de l'appel de vérification.

Valider le document JWKS vaut la peine même après la migration. Un point de terminaison JWKS qui renvoie {"kty":"oct","k":""} est un bogue de configuration à lui seul, et tout vérificateur qui l'accepte comme matériel de clé échoue en mode ouvert. Rejetez le document et alertez lorsqu'une clé symétrique a un k vide.

Si vous ne pouvez pas changer le code immédiatement, remplacez la clé de vérification concernée par une valeur aléatoire forte. Cela ne ferme l'exposition que si chaque déploiement reçoit la nouvelle valeur ; une clé vide reste exploitable partout où la configuration est encore vide.

Quelle est la cause racine ?

python-jose valide le type d'une clé HMAC mais jamais sa longueur, et le chemin JWK transforme un membre k vide en octets vides. La construction HMAC utilise alors cette clé vide, dont la signature est calculable par n'importe qui.

Dans jose/backends/native.py à la version 3.5.0, HMACKey.__init__ accepte toute valeur de type chaîne ou octets :

def __init__(self, key, algorithm):
    if algorithm not in ALGORITHMS.HMAC:
        raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
    self._algorithm = algorithm
    self._hash_alg = self.HASHES.get(algorithm)

    if isinstance(key, dict):
        self.prepared_key = self._process_jwk(key)
        return

    if not isinstance(key, str) and not isinstance(key, bytes):
        raise JWKError("Expecting a string- or bytes-formatted key.")

    if isinstance(key, str):
        key = key.encode("utf-8")

    if is_pem_format(key) or is_ssh_key(key):
        raise JWKError(
            "The specified key is an asymmetric key or x509 certificate and"
            " should not be used as an HMAC secret."
        )

    self.prepared_key = key

Il n'y a pas de contrôle len(key) == 0. _process_jwk décode le membre k avec base64url_decode, et cette fonction renvoie une chaîne d'octets vide pour une entrée vide :

def _process_jwk(self, jwk_dict):
    if not jwk_dict.get("kty") == "oct":
        raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty"))

    k = jwk_dict.get("k")
    k = k.encode("utf-8")
    k = bytes(k)
    k = base64url_decode(k)

    return k

CryptographyHMACKey dans jose/backends/cryptography_backend.py répète la même logique de constructeur et présente le même écart. Les deux backends calculent ensuite hmac.new(self.prepared_key, msg, self._hash_alg) et comparent le résultat avec hmac.compare_digest ou h.verify. Avec une clé vide, ce calcul est déterministe et public, donc la comparaison réussit pour une signature produite par l'attaquant.

Le chemin JWK rend la faille atteignable depuis la configuration plutôt que depuis une chaîne vide évidente. jws._get_keys() traite un mapping avec kty comme un JWK individuel, jwk.construct() renvoie la classe de clé HMAC pour un algorithme HS, et le k vide devient b"" avant la vérification.

jws._verify_signature() lit alg dans l'en-tête du jeton, construit la clé pour chaque candidat et appelle key.verify(). Avec une clé vide, cet appel renvoie vrai pour la signature de l'attaquant, et le reste de la validation JWT continue avec les revendications choisies par l'attaquant.

Comment la faille est-elle exploitée ?

L'attaquant a besoin d'un point de terminaison de jeton ou de toute requête portant un JWT, et de savoir ou de deviner que la clé du déploiement est vide :

  1. L'attaquant trouve une application qui vérifie des jetons HS256 avec python-jose et dont la clé est vide, par exemple parce que JWT_SECRET n'est pas défini et que le code passe os.environ.get("JWT_SECRET", "").
  2. L'attaquant construit un jeton avec les revendications auxquelles l'application fait confiance, comme {"sub": "admin", "role": "admin"}, et le signe avec une clé HMAC vide.
  3. L'attaquant envoie le jeton. python-jose construit HMACKey(b"", "HS256"), calcule la même signature et accepte le jeton.
  4. L'application agit sur les revendications forgées, car elle ne voit aucun problème de clé.

La preuve de concept du rapporteur est publique dans le ticket #398 et s'exécute telle quelle :

import jose.jwt as jwt

# This succeeds and should raise an error.
token = jwt.encode({"admin": True}, "", algorithm="HS256")
jwt.decode(token, "", algorithms=["HS256"])  # Returns {"admin": True}

La même chose fonctionne avec la forme JWK, celle qu'utilise un déploiement piloté par JWKS :

jwt.decode(token, {"kty": "oct", "k": ""}, algorithms=["HS256"])

Comment se déroule l'attaque ?

Une application qui laisse sa clé de vérification HMAC devenir vide transmet à python-jose un secret de longueur nulle, et python-jose signe et vérifie avec au lieu de rejeter la configuration.

flowchart LR
    A["La clé de l'application devient vide"] --> B["HMACKey accepte une clé de longueur nulle"]
    B --> C["L'attaquant calcule le HMAC avec la clé vide"]
    C --> D["jwt.decode compare les signatures"]
    D --> E["Revendications forgées acceptées"]

Chronologie de CVE-2026-120459 : python-jose 3.5.0 est sorti le 28 mai 2025, le ticket public a été ouvert le 8 mars 2026, et le CVE a été publié le 2 novembre 2026
Figure 1 : La faille a été signalée en mars 2026 et a reçu un CVE en novembre 2026, sans version corrigée entre-temps. Source : le ticket GitHub, l'historique des versions PyPI et l'enregistrement CVE, vérifiés le 9 novembre 2026.

Que peut faire un attaquant ?

Avec un jeton forgé accepté, l'attaquant prend toutes les revendications auxquelles l'application fait confiance. Cela peut inclure :

  • N'importe quelle identité d'utilisateur, y compris un administrateur, lorsque l'application autorise à partir des revendications.
  • N'importe quel identifiant de locataire ou d'organisation utilisé pour délimiter les données.
  • Une expiration prolongée, car l'attaquant choisit la revendication exp.
  • N'importe quelle permission ou rôle lu dans le jeton.

L'attaquant ne peut pas récupérer une vraie clé de signature, déchiffrer d'autres jetons, ni signer des jetons qu'un déploiement correctement configuré accepte. L'impact dépend de ce que l'application fait des revendications et de la présence de la condition de clé vide.

Les jetons de service à service comptent aussi. Un jeton forgé pour une API interne peut franchir une frontière de confiance qui ne voit jamais d'utilisateur. Une clé vide dans un service en arrière-plan peut donc exposer davantage qu'un point de terminaison de connexion.

Toute requête portant un jeton bearer est candidate, y compris les tâches CI et les appels de service internes. Un déploiement avec un secret fort et non vide et une liste blanche d'algorithmes explicite n'est pas exploitable par cette faille.

Pourquoi le correctif fonctionne-t-il ?

Une clé HMAC de longueur nulle n'a pas de secret : n'importe qui peut calculer le même MAC, donc un vérificateur qui en reçoit une n'authentifie rien. La correction est un contrôle de longueur côté application ou un passage à une bibliothèque qui l'applique.

Le contrôle qu'une version corrigée doit ajouter est petit. Dans les deux backends, immédiatement après le contrôle de type :

     if not isinstance(key, str) and not isinstance(key, bytes):
         raise JWKError("Expecting a string- or bytes-formatted key.")

+    if len(key) == 0:
+        raise JWKError("HMAC key must not be empty.")
+
     if isinstance(key, str):
         key = key.encode("utf-8")

Le chemin JWK a besoin de son propre contrôle après base64url_decode, car un k vide n'atteint jamais la branche chaîne.

PyJWT effectue le même contrôle de longueur et a été vérifié localement le 2026-11-09 avec un encodage et un décodage HS256. Cela fait de la migration une correction de cette faille, pas seulement un changement de bibliothèque.

Le ticket python-jose liste quatre autres constats dans le même rapport, dont la confusion d'algorithme avec des clés DER et la valeur par défaut algorithms=None. La migration corrige la faille de clé vide et évite de dépendre d'un projet non maintenu pour les autres.

Comment vérifier si j'ai été compromis ?

Un jeton forgé ressemble à un jeton valide, donc le contrôle de signature ne peut pas vous dire qu'il a été forgé. Cherchez d'abord la condition de configuration, puis l'activité de compte que le vrai utilisateur n'a pas effectuée.

  1. Confirmez si un déploiement peut transmettre une clé vide. Examinez les valeurs par défaut de configuration et le code de chargement des clés, et interrogez le point de terminaison JWKS utilisé.
  2. Décodez les jetons acceptés par votre service pendant la fenêtre d'exposition et cherchez des revendications qui ne correspondent pas au compte, comme un sub, un rôle ou un locataire inattendu. La charge utile est lisible sans la clé.
# Show a token's header and claims without verifying it.
python - <<'PY'
import base64, json, sys
token = sys.stdin.read().strip()
for part in token.split(".")[:2]:
    part += "=" * (-len(part) % 4)
    print(json.dumps(json.loads(base64.urlsafe_b64decode(part)), indent=2))
PY
  1. Cherchez dans les journaux d'authentification des jetons avec un iat, exp ou sub inhabituel pour le compte. Cherchez ensuite les actions administratives de sessions qui ne peuvent pas être rattachées à une connexion réelle.
  2. Remplacez la clé de vérification par une valeur aléatoire forte et invalidez les sessions existantes. Si la clé était vide, tout jeton signé pendant cette période doit être considéré comme non fiable.
  3. Passez à PyJWT ou ajoutez le contrôle, puis retestez qu'une clé vide est rejetée.

Une tentative de falsification échouée peut ne laisser aucune trace au-delà de la requête elle-même, car une signature rejetée et une signature forgée suivent le même chemin de code jusqu'à la comparaison.

Chronologie

  • 2025-05-28 : python-jose 3.5.0 est publié, la dernière version au 2026-11-09.
  • 2026-03-08 : le rapporteur ouvre le ticket #398 avec le constat de clé vide et une preuve de concept.
  • 2026-11-02 : VulnCheck publie CVE-2026-120459.

FAQ

Mon application est-elle concernée si mon secret HMAC est une valeur aléatoire forte ?

Non. La faille exige que la clé de vérification prenne une valeur vide. Un secret fort et non vide rend le calcul de falsification impossible pour un attaquant, même sur une version python-jose concernée.

Passer algorithms=["HS256"] corrige-t-il la faille de clé vide ?

Non. La liste blanche limite les algorithmes acceptés par le vérificateur, mais elle ne vérifie pas la longueur de la clé. Un jeton signé en HS256 avec une clé vide est toujours vérifié lorsque l'application transmet une clé vide et autorise HS256.

Existe-t-il une version corrigée de python-jose ?

Non. La dernière version reste 3.5.0 du 2025-05-28, et l'enregistrement CVE ne nomme aucune version corrigée. Utilisez le contrôle applicatif ou migrez vers une bibliothèque maintenue.

PyJWT a-t-il le même problème ?

Non. PyJWT 2.15.1 rejette une clé HMAC vide avec InvalidKeyError("HMAC key must not be empty."). Cela a été vérifié localement le 2026-11-09 avec un encodage et un décodage HS256.

Le changement de clé aide-t-il ?

Oui, si chaque déploiement reçoit la nouvelle valeur. Le remplacement par un secret aléatoire fort supprime la condition de clé vide dont dépend la falsification, et il invalide aussi les jetons signés avec l'ancienne clé. Une clé vide reste exploitable partout où la configuration est encore vide, donc associez le changement au contrôle.

Un attaquant peut-il exploiter la faille sans savoir que la clé est vide ?

L'attaquant a besoin que la condition de clé vide soit vraie. Certains déploiements la rendent devinable, par exemple une variable d'environnement non définie avec une valeur par défaut vide ou une entrée JWKS avec k: "". Une fois la condition remplie, l'attaquant calcule la signature localement et n'a besoin d'aucun autre accès.

Trouver les services utilisant python-jose avec Ostorlab

L'analyse de composition logicielle et l'inventaire SBOM d'Ostorlab listent les paquets Python et les versions que vos services résolvent. Cela vous permet de trouver les applications qui dépendent encore de python-jose et de les faire passer par le contrôle de clé vide ou une migration.

Références

  • Avis VulnCheck, « python-jose through 3.5.0 Authentication Bypass via Empty HMAC Key » (2026-11-02) : https://www.vulncheck.com/advisories/python-jose-through-3.5.0-authentication-bypass-via-empty-hmac-key (vérifié le 9 novembre 2026)
  • Enregistrement CVE CVE-2026-120459 (2026-11-02) : https://www.cve.org/CVERecord?id=CVE-2026-120459 (vérifié le 9 novembre 2026)
  • Ticket python-jose #398, « Security: DER key algorithm confusion, empty HMAC key, and timing side-channels » (2026-03-08) : https://github.com/mpdavis/python-jose/issues/398 (vérifié le 9 novembre 2026)
  • python-jose 3.5.0, jose/backends/native.py (2025-05-28) : https://github.com/mpdavis/python-jose/blob/3.5.0/jose/backends/native.py (vérifié le 9 novembre 2026)
  • python-jose 3.5.0, jose/backends/cryptography_backend.py (2025-05-28) : https://github.com/mpdavis/python-jose/blob/3.5.0/jose/backends/cryptography_backend.py (vérifié le 9 novembre 2026)
  • PyJWT 2.15.1, HMACAlgorithm.prepare_key (vérifié le 9 novembre 2026) : https://github.com/jpadilla/pyjwt/blob/2.15.1/jwt/algorithms.py
  • Page PyPI de python-jose, historique des versions (vérifié le 9 novembre 2026) : https://pypi.org/project/python-jose/
  • pypistats, téléchargements hebdomadaires de python-jose (vérifié le 9 novembre 2026) : https://pypistats.org/packages/python-jose