Sommaire

Tokens JWT : Comment ils fonctionnent et comment les implémenter en toute sécurité

Introduction

Les JSON Web Tokens (JWT) sont un moyen d'envoyer des informations sous forme de jeton. Ils sont souvent utilisés pour authentifier les utilisateurs, mais peuvent également servir à d'autres fins, telles que la gestion des réinitialisations de mot de passe.

Dans cet article, nous allons explorer ce que sont les tokens JWT, comment ils fonctionnent et comment les implémenter correctement.

Décomposer un token JWT

Examinons cette chaîne : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmFtZSI6IlF1aW50ZXNzZW5jZSIsImlhdCI6MTUxNjIzOTAyMn0.iyJdZNCwXy_QL8Jba8p0apBQKGYx087C09uePV2w1ic

Vous pouvez remarquer un certain schéma, avec trois parties distinctes encodées en base64.

  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  2. eyJzdWIiOiIxIiwibmFtZSI6IlF1aW50ZXNzZW5jZSIsImlhdCI6MTUxNjIzOTAyMn0
  3. iyJdZNCwXy_QL8Jba8p0apBQKGYx087C09uePV2w1ic

Le header (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9) contient des informations sur le token, son algorithme et son type.

Vous pouvez le lire ainsi :

1$ echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
2{"alg":"HS256","typ":"JWT"}

Et nous découvrons que notre algorithme est HS256, ce qui signifie HMAC-SHA256.

Il existe deux autres familles d'algorithmes couramment utilisées : RS256 et ES256.

PS256 existe également, mais est moins fréquent.

Nous aborderons plus en détail les différents algorithmes plus tard.

Payload

Le payload contient les données réelles que nous voulons transmettre.

1$ echo "eyJzdWIiOiIxIiwibmFtZSI6IlF1aW50ZXNzZW5jZSIsImlhdCI6MTUxNjIzOTAyMn0" | base64 -d
2{"sub":"2","name":"Quintessence","iat":1516239022}

Il s'agit d'un objet JSON qui contient les informations suivantes :

  • sub : Le sujet du token, qui est ici l'identifiant de l'utilisateur.
  • iat : Le timestamp de création, soit le moment où le token a été généré.
    Ces deux champs sont standards et définis dans le RFC 7519.

D'autres champs standard existent mais sont moins courants :

  • iss (Issuer) : Identifie l'émetteur du JWT. Chaîne sensible à la casse (StringOrURI).
  • sub (Subject) : Identifie le sujet du JWT. Doit être unique dans le contexte de l'émetteur ou globalement. Chaîne sensible à la casse (StringOrURI).
  • aud (Audience) : Spécifie les destinataires prévus du JWT. Peut être une chaîne ou un tableau de chaînes.
  • exp (Expiration Time) : Définit la date d'expiration après laquelle le JWT ne doit plus être accepté. Valeur NumericDate.
  • nbf (Not Before) : Définit le moment avant lequel le JWT ne doit pas être accepté. Valeur NumericDate.
  • iat (Issued At) : Spécifie la date de création du JWT. Valeur NumericDate.
  • jti (JWT ID) : Fournit un identifiant unique pour le JWT afin d'éviter les attaques de rejeu. Chaîne sensible à la casse.

Signature

C'est la dernière et la plus importante partie du token. Elle est calculée à l'aide de l'algorithme spécifié dans le header et permet de vérifier que le token a été créé par l'émetteur.

Cela signifie qu'un JWT peut être lu par n'importe qui, mais seule l'entité émettrice peut créer une signature valide grâce à la clé secrète.

Étant donné que cette partie n'est ni JSON ni texte, nous utiliserons un hexdump pour voir ce qu'elle contient :

1$ echo "iyJdZNCwXy_QL8Jba8p0apBQKGYx087C09uePV2w1ic" | base64 -di | xxd
200000000: 8b22 5d64 d0b0 5f24 0bf0 96da f29d 1aa4  ."]d.._$........
300000010: 140a 198c 74f3 b0b4 f6e7 8f57 6c35 89    ....t......Wl5.

(notez que l'option i est passée à la commande base64, c'est-à-dire --ignore-garbage)

Nous voyons une séquence de bytes qui semble aléatoire, mais cette séquence exacte est la signature HMAC-SHA256 (HS256) du header et du payload calculée à l'aide de la clé secrète. Sans cette clé, il est impossible de créer une signature valide et donc, le serveur rejettera le token.

Algorithmes de signature

HS256 (HMAC-SHA256)

Principe : Utilise une clé secrète partagée avec HMAC et SHA-256 pour signer et vérifier le JWT. La même clé est utilisée pour signer et vérifier (symétrique).

Avantages :

  • Simple et rapide.
  • Efficace pour les systèmes de petite taille.

Inconvénients :

  • La clé partagée augmente le risque d’exposition.
  • Inadapté pour les systèmes multi-parties ou distribués.

RS256 (RSA-SHA256)

Principe : Utilise RSA avec SHA-256 pour signer. La clé privée signe le JWT, et la clé publique le vérifie (asymétrique).

Avantages :

  • Le modèle clé publique/privée offre flexibilité et sécurité.
  • Bien supporté et largement compris.

Inconvénients :

  • Les clés plus grandes entraînent une performance plus lente.
  • Nécessite une gestion rigoureuse des clés.

ES256 (ECDSA-SHA256)

Principe : Utilise l'algorithme de signature numérique à courbes elliptiques (ECDSA) avec SHA-256 pour signer. La clé privée signe et la clé publique vérifie (asymétrique).

Avantages :

  • Offre une meilleure sécurité avec des clés plus petites.
  • Plus efficace pour des environnements à ressources limitées (ex : IoT).

Inconvénients :

  • Plus lent que HS256.
  • Plus complexe à implémenter que RSA.

PS256 (RSA-PSS avec SHA-256)

Principe : Utilise le schéma de signature probabiliste RSA (PSS) avec SHA-256 pour signer. La clé privée signe et la clé publique vérifie (asymétrique).

Avantages :
-Remplissage plus sécurisé comparé à RS256, résistant à certaines attaques.
-Idéal pour des applications hautement sécurisées.

Inconvénients :

  • Intensif en calcul.
  • Nécessite des bibliothèques cryptographiques modernes pour l’implémentation.

Attaquer les tokens JWT

Les tokens JWT sont vulnérables à plusieurs attaques. Voici deux des attaques les plus courantes.

Dans notre exemple, nous sommes un utilisateur malveillant cherchant à s’authentifier comme un utilisateur légitime.

Replay attacks CWE-294

Cette attaque est simple et très courante. Cependant, son impact réel est faible, car il faut capturer un token via un trafic HTTPS ou attaquer la machine de l’utilisateur avec un infostealer (en).

Si vous parvenez à voler un token, il suffit de l’envoyer au serveur via l’en-tête Authorization (ou un cookie, selon l’implémentation distante). Vous obtenez alors une session valide tant que le token n’a pas expiré. Cela signifie que le champ iat n’est pas trop éloigné dans le futur ou que le champ exp n’est pas dans le passé.

Souvent, l’implémentation du serveur est défectueuse et ne met pas le token sur liste noire si l’utilisateur a prématurément mis fin à la session en se déconnectant, car un JWT ne peut pas être révoqué aussi facilement qu’un cookie de session.

Vérification incorrecte de la signature CWE-347

Si l’application web est paresseusement implémentée, elle pourrait ne pas vérifier la signature du token, permettant ainsi de décoder le payload, de le modifier, de le réencoder et d’envoyer un token forgé au serveur.

La signature du token serait invalide, mais le serveur pourrait l’accepter, vous accordant ainsi une session valide sur l’application web.

Signature de clé faible CWE-1391

La clé de signature est la partie la plus importante du token JWT. C’est la seule chose qui peut être modifiée.

En utilisant HS256, la clé secrète est une simple chaîne de caractères, contrairement à RSA ou ECDSA qui disposent d’outils CLI pour générer des paires de clés valides. Cela signifie que le développeur doit générer une clé manuellement, qui pourrait être une chaîne aléatoire cryptographiquement sécurisée ou… password, par exemple.

Dans ce cas, l’attaquant peut utiliser hashcat et une wordlist pour tenter de trouver la clé secrète. Si cette clé est un mot de passe courant, c’est terminé.

Reforger une signature valide est aussi simple que d’exécuter ce script Python nécessitant la bibliothèque PyJWT :

1import jwt
2encoded = jwt.encode({"sub":"1","name":"Admin","iat":1516239022}, "password", algorithm="HS256")
3print(encoded)

Ce qui nous donne : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmFtZSI6IkFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.uXUMnVnIA3FiDcuS4tSr4q2t3tA3frmCw38eXgIabGc

Si on vérifie avec CyberChef, le JWT est entièrement valide.

Vulnérabilité de confusion d'algorithme

En 2023, la bibliothèque NodeJS fast-jwt a été identifiée comme vulnérable à des attaques de confusion d'algorithme, permettant à un attaquant de forger un jeton JWT avec un algorithme différent de celui utilisé par le serveur.

Cette vulnérabilité a reçu l'identifiant CVE-2023-48223 dans le cadre du système des Common Vulnerabilities and Exposures (CVE).

La vulnérabilité exploitait une mauvaise gestion des clés publiques au format PEM et l'absence d'application stricte de l'algorithme attendu lors de la vérification des JWT. Plus précisément, elle permettait à un attaquant de créer un jeton JWT malveillant utilisant l'algorithme HS256 tout en le signant avec la clé publique RSA du serveur. Cette exploitation reposait sur le fait que la fonction publicKeyPemMatcher de la bibliothèque ne correspondait pas correctement à tous les formats PEM courants, en particulier l'en-tête BEGIN RSA PUBLIC KEY.

En obtenant la clé publique RSA du serveur — potentiellement dérivée à partir de deux jetons JWT — l'attaquant pouvait exploiter cette faille. En utilisant des outils comme rsa_sign2n pour extraire les informations de clé publique et jwt_tool pour forger des jetons, l'attaquant pouvait signer des charges utiles arbitraires. Ces jetons forgés étaient acceptés comme valides par le serveur si ce dernier ne faisait pas appliquer explicitement l'algorithme attendu (par exemple, RS256).

Cette vulnérabilité souligne l'importance d'appliquer strictement l'algorithme attendu lors de la vérification des jetons JWT et de valider les formats de clé. Les applications utilisant RS256 avec des clés publiques contenant un en-tête BEGIN RSA PUBLIC KEY étaient particulièrement exposées, mettant en évidence la nécessité de mettre à jour rigoureusement les bibliothèques et de pratiquer une validation stricte des algorithmes.

Points clés pour implémenter correctement les tokens JWT

Pour implémenter les tokens JWT de manière sécurisée, assurez-vous de :

  1. Si vous utilisez HS256, utilisez une clé secrète robuste. La commande tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_{|}~' </dev/random | head -c 64 peut être utilisée pour générer une chaîne aléatoire cryptographiquement sécurisée dans votre terminal sur les systèmes de type Unix.
  2. Privilégiez l’utilisation de RS256 ou ES256. Utilisez un générateur de paire de clés approprié, tel que OpenSSL.
  3. Définissez des temps d’expiration courts et privilégiez le renouvellement du token lorsqu’il est sur le point d’expirer pour éviter les attaques par rejeu (replay attacks).
  4. Placez le token sur une liste noire si l’utilisateur se déconnecte manuellement. De cette façon, si le token est volé, le serveur le rejettera.

Appliquer ces 4 points suffit à renforcer votre application contre la plupart des attaques liées aux tokens JWT.

Conclusion

Les tokens JWT sont un outil puissant pour la communication et l’authentification sécurisées lorsqu’ils sont correctement implémentés. Ils offrent un moyen compact et autonome de partager des informations entre différentes parties. Cependant, leur flexibilité et leur adoption généralisée comportent des risques, en particulier s’ils ne sont pas mis en œuvre de manière sécurisée.

Cet article a présenté l’anatomie d’un JWT, exploré différents algorithmes de signature et discuté des vulnérabilités courantes telles que les attaques par rejeu, la vérification incorrecte des signatures et les clés de signature faibles. En suivant les recommandations d’implémentation, notamment l’utilisation de clés robustes, de durées d’expiration courtes et la mise en liste noire des tokens lors de la déconnexion, les développeurs peuvent considérablement atténuer ces risques et améliorer la sécurité de leurs applications.

Nos services

Nous sommes une équipe de consultants en cybersécurité dédiée à identifier les faiblesses et à aider les organisations à renforcer leur posture de sécurité.

Nous garantissons la confidentialité grâce à des communications chiffrées via PGP et acceptons toutes les clauses de confidentialité que vous pourriez proposer.

Nous sommes spécialisés dans les tests d’intrusion (pentesting), l’audit de code et la surveillance pour garantir la sécurité de vos services et de votre infrastructure.

Vous pouvez nous contacter à [email protected] si vous avez des questions ou si vous avez besoin d’aide pour vos besoins en cybersécurité.