Les aspects complexes de la sécurité des JWT dont personne ne parle

10 janv. 2019
-minutes de lecture
Expert en sécurité web, Fondateur de Pragmatic Web Security

Introduction

Aujourd’hui, quasiment tous les développeurs web utilisent des JSON Web Tokens d’une manière ou d’une autre. OAuth 2.0 et OpenID Connect les utilisent pour échanger des informations entre les parties. Les applications actuelles les utilisent pour contrôler le statut entre les différentes requêtes. Les services de ‘back office’ les utilisent pour diffuser des informations d’autorisation dans une architecture de microservice.

Malgré leur popularité, les propriétés de sécurité des JWT sont souvent mal connues ou mal comprises. Comment choisir le schéma de signature pour un JWT ? Quelles autres propriétés faut-il vérifier avant de faire confiance à un JWT ? Comment mener à bien la rotation des clés et leur gestion ?

Les réponses à ces questions sont cruciales pour garantir la sécurité de l’architecture de l’application. Dans cet article, nous allons plus loin que le discours habituel sur l’utilisation des JWT. Nous examinons les aspects complexes dont personne ne parle jamais, notamment :
 

  • Les signatures symétriques JWT

  • Les signatures asymétriques JWT

  • La validation des JWT au-delà des signatures

  • La gestion des clés de chiffrement

  • L’utilisation, en pratique, des JWT
     

REMARQUE : si vous n’avez pas une connaissance de base des JWT, nous vous suggérons de lire une introduction aux JWT avant de vous plonger dans cet article.

Signatures JWT symétriques

La capture d’écran de la Figure 1 ci-dessous montre la décomposition d’un JWT avec ses trois composants. Comme vous pouvez le voir, la partie située au milieu du JWT contient les données réelles. L’en-tête inclut les métadonnées sur le jeton, et la signature est là pour garantir l’intégrité. La signature est essentielle pour détecter des modifications non autorisées d’un jeton.

 

Figure 1 Un JWT signé avec un HMAC exige le même secret pour vérifier la signature.

 

Fonctionnement

Lorsqu’un service génère un JWT, il crée aussi une signature. Traditionnellement, cette signature est un HMAC, qui utilise un type particulier de fonctions cryptographiques. Cet algorithme HMAC est signalé avec le préfixe « HS », comme le montre l’exemple de jeton ci-dessus.

 

Le HMAC prend l’en-tête, la charge utile et une clé secrète comme entrées, et renvoie une signature unique aux trois entrées. Ce processus est illustré dans la partie gauche de la Figure 2 ci-dessous.

 

 

 

Figure 2 Ce schéma montre comment générer et vérifier un JWT avec une clé symétrique.

 

Lorsqu’un service reçoit un JWT entrant, il doit en vérifier l’intégrité avant d’utiliser les données intégrées. Pour ce faire, le service utilise la même clé secrète pour calculer le HMAC du JWT. Si le HMAC qui en résulte est le même que la signature du jeton, le service saura que les trois entrées de la fonction HMAC étaient les mêmes qu’avant.

Par contre, si le HMAC ne correspond pas, cela signifie que quelque chose a changé. Il est peu probable que la clé secrète change ; c’est donc quelque chose dans le JWT entrant qui a changé. Ce qui a changé importe peu au service. Il rejette tout simplement le JWT. Ce processus est présenté sur la partie droite de la Figure 2 ci-dessus.

 

Limites des signatures symétriques

Ce schéma de signature est simple. C’est également le schéma typique utilisé pour expliquer les JWT aux développeurs. Malheureusement, les signatures symétriques empêchent le partage du JWT avec un autre service. Pour vérifier l’intégrité du JWT, tous les services devraient accéder à la même clé secrète. Toutefois, la possession d’une clé secrète suffit pour générer des JWT arbitraires avec une signature valide.

 

Partager le secret HMAC avec un service tiers engendre une importante vulnérabilité. Il n’est pas non plus recommandé de partager le secret entre différents services au sein d’une même architecture. Si tous les services partagent la même clé, il suffit qu’un seul service soit compromis pour engendrer immédiatement un problème au niveau de l’architecture.

 

Au lieu de partager la clé HMAC secrète, vous pouvez opter pour des signatures asymétriques.

Les signatures asymétriques JWT

Une signature asymétrique utilise une paire de clés publique/privée. Cette paire de clés a une propriété unique. Une signature générée avec une clé privée peut être vérifiée avec la clé publique. Et comme son nom l’implique, la clé publique peut être partagée avec d’autres services. La Figure 3 ci-dessous montre un JWT avec une signature asymétrique.

 

Figure 3 Un JWT signé avec une clé privée peut être vérifié avec la clé publique correspondante.

 

Fonctionnement

Sur la Figure 4 ci-dessous, le processus de génération d’une signature est présenté sur la gauche. La méthode pour vérifier la signature apparaît sur la droite.

 

 

 

Figure 4 Ce schéma illustre le processus de signature avec une clé privée et de vérification avec une clé publique.

 

Comme vous pouvez le constater, celui qui émet un jeton utilise uniquement la clé privée. Cela implique que la clé privée puisse être conservée dans un endroit confidentiel, que seul l’émetteur des jetons JWT connaît. La clé publique peut être largement distribuée, afin que chaque consommateur puisse vérifier son intégrité. Les algorithmes qui génèrent cette signature sont signalés avec le préfixe « HS », comme indiqué sur l’exemple de jeton présenté précédemment.

 

Les signatures asymétriques sur OpenID Connect

OpenID Connect est l’un des protocoles les plus courants à utiliser ce schéma de signature. Par exemple, lorsque vous utilisez la fonction « Se connecter avec Google », vous utilisez OpenID Connect. À la fin de cette procédure, Google fournit un jeton d’identité à l’application. Ce jeton d’identité contient des informations sur votre identité avec Google. De ce fait, il indique qui vous êtes à l’application, ce qui fait d’OpenID Connect un protocole d’authentification.

 

Dans le scénario OpenID Connect, Google utilise ses clés privées pour signer le jeton d’identité. L’application utilise la clé publique de Google pour vérifier son intégrité, avant de se fier aux données intégrées.

 

De même, les déploiements des entreprises qui utilisent OpenID Connect pour le single sign-on (SSO) s’appuient également sur des JWT. Le service de SSO signe les jetons avec une clé privée. Chaque application de consommation vérifie l’intégrité en utilisant la clé publique.

 

 

Les avantages des signatures asymétriques

Les signatures asymétriques ne se limitent pas à OpenID Connect. Chaque scénario distribué utilisant les JWT bénéficie de l’utilisation de ce schéma de signature. Par exemple, dans une architecture microservice où des JWT sont échangés, chaque service peut avoir une paire de clés publique/privée. Par rapport aux signatures symétriques, ce schéma réduit considérablement l’impact d’une faille dans un seul service de cette architecture.

 

La validation des JWT au-delà des signatures


L’utilisation des JWT apporte une sécurité qui va au-delà de la vérification des signatures. Outre la signature, le JWT peut contenir d’autres propriétés liées à la sécurité. Ces propriétés se présentent sous la forme de revendications réservées qui peuvent être incluses dans le corps du JWT.

La revendication de sécurité la plus importante est la revendication « exp ». L’émetteur utilise cette revendication pour indiquer la date d’expiration d’un JWT. Si cette date d’expiration est dépassée, le JWT a expiré et ne doit plus être utilisé. Un exemple de cas d’usage typique est celui d’un jeton d’identité OpenID Connect, qui expire après une période donnée.

Une deuxième réclamation qui y est liée est la revendication « iat ». Cette revendication indique le moment où le JWT a été délivré. Elle est souvent utilisée pour permettre au consommateur du JWT de décider si le jeton est suffisamment frais ou non. Si ce n’est pas le cas, le consommateur peut rejeter le JWT en faveur d’un jeton venant d’être délivré.

Troisièmement, les JWT peuvent contenir la réclamation « nbf ». Cette abréviation signifie « not before » (« pas avant »). Elle indique à partir de quand le JWT devient valide. Un JWT ne peut être accepté que si cette référence temporelle se situe dans le passé.

La quatrième revendication liée à la sécurité est « iss ». Cette revendication indique l’identité de la partie qui a émis le JWT. Elle inclut une simple chaîne, dont la valeur est à la discrétion de l’émetteur. Le consommateur d’un JWT devrait toujours vérifier que la revendication « iss » correspond à l’émetteur attendu (par exemple : sss.exemple.com).

La cinquième revendication à mentionner est la revendication « aud ». Cette abréviation signifie « audience », « public ». Elle indique celui/ceux à qui le jeton est destiné. Le consommateur d’un JWT devrait toujours vérifier que le public correspond à son propre identifiant. La valeur de cette revendication est là-encore un string de valeur, à la discrétion de l’émetteur. Dans les scénarios OAuth 2.0 et OpenID Connect, cette valeur contient typiquement l’identifiant du client (par ex. : api.exemple.com).

La spécification mentionne, notons-le, que toutes ces revendications sont facultatives. Néanmoins, il est fortement recommandé que votre application les inclue lorsqu’elle délivre des JWT. De même, leur présence doit être vérifiée lorsque les JWT sont validés. Cela permet de prévenir les abus lorsque les JWT sont exposés d’une manière ou d’une autre.


Ci-dessous figure un exemple de code pour vérifier ces revendications en utilisant la célèbre bibliothèque « java-jwt ». Comme vous pouvez le constater, cette bibliothèque propose des fonctions dédiées pour vérifier ces revendications. Consultez vos bibliothèques pour découvrir comment gérer ces revendications de manière optimale.

Algorithm algorithm = Algorithm.HMAC256(HMAC_KEY);
JWTVerifier verifier = JWT.require(algorithm)                
.withIssuer("sso.pragmaticwebsecurity.com")
     .withAudience("api.pragmaticwebsecurity.com")
     .build();
DecodedJWT verifiedJWT = verifier.verify(token);
         
// Get the subject
verifiedJWT.getSubject();

Gestion des clés de chiffrement

Ce qui peut poser le plus de difficulté lorsqu’on utilise un JWT, c’est la gestion des clés de chiffrement. Les clés de chiffrement utilisées pour les signatures et le chiffrement doivent pivoter régulièrement. La réalisation d’un nombre trop important d’opérations avec une clé ouvre l’application à des attaques de cryptanalyse. Malheureusement, la rotation de la clé est souvent négligée dans les applications qui utilisent des JWT.

Le problème de la rotation de clé n’est pas facile à résoudre. Pour faire pivoter les clés, il faut que plusieurs clés soient utilisées en même temps. Il faut également que l’émetteur et le consommateur récupèrent les clés de manière dynamique. Voyons comment gérer en pratique une clé de chiffrement pour les JWT.

 

Identifier une clé

La suite de spécifications sur les dispositions d’un JWT offre plusieurs options pour identifier des clés de chiffrement en particulier. Le mécanisme le plus simple est la revendication « kid ». Cette revendication peut être ajoutée à l’en-tête du jeton. Elle vise à contenir un identifiant de clé basé sur le string. La Figure 5 ci-dessous en donne un exemple.

 

Figure 5 La revendication avec en-tête « kid » peut être utilisée pour identifier une clé en particulier.

 

Avec l’identifiant de la clé, le consommateur d’un JWT peut récupérer la bonne clé de chiffrement pour vérifier la signature. Ce mécanisme simple fonctionne bien quand tant l’émetteur que le consommateur ont accès au même magasin de clés de chiffrement. Par exemple, un ensemble de services fonctionnant dans une zone de confiance peut accéder au même coffre de clés. L’identifiant de la clé identifie ensuite soit une clé utilisée pour un HMAC ou une paire de clés publique/privée utilisée pour les signatures asymétriques.

Toutefois, une configuration plus dynamique est nécessaire dans plusieurs scénarios. Imaginez que vous devez vérifier le jeton d’identité émis dans un flux OpenID Connect. Les clés utilisées par l’émetteur pivoteront régulièrement. Si l’émetteur réside dans une autre zone de confiance, il est peu probable que tant l’émetteur que le consommateur puissent accéder au même magasin de clés de confiance. Dans ces cas-ci, la spécification fournit un mécanisme de configuration plus dynamique.

 

 

Intégrer des clés

Une première option pour distribuer une clé publique est de l’intégrer directement dans l’en-tête du JWT. Le consommateur peut récupérer la clé et l’utiliser pour vérifier la signature. La spécification fournit ici deux mécanismes. Le premier est la revendication « jwk », qui est conçue pour inclure une clé publique dans le format JSON Web Key. Le second est la revendication « x5c », visant à inclure une clé publique sous le format d’un certificat X509.


Intégrer la clé dans le jeton est une manière simple de permettre sa distribution. Pour garantir la sécurité de ce mécanisme, le consommateur de JWT doit restreindre les clés qu’il accepte. S’il ne le fait pas, un attaquant pourra générer des jetons signés avec une clé privée malveillante.

Un consommateur trop permissif utiliserait simplement la clé publique intégrée pour vérifier la signature, qui sera valide. Pour éviter ces problèmes, le consommateur doit faire correspondre la clé utilisée à une liste blanche explicite de clés. Si la clé se présente sous la forme d’un certificat X509, le consommateur pourra utiliser les informations figurant sur le certificat pour en vérifier l’authenticité.

 

Distribuer les clés

La deuxième option pour distribuer les clés de manière dynamique est d’utiliser les URL des clés. La revendication « jku » doit contenir une URL, qui indique un ficher contenant des clés dans un format JSON Web Key. Ce mécanisme est souvent utilisé dans le contexte OpenID Connect pour la récupération automatique des clés. L’endpoint de l’API de Google en fournit un exemple. Comme précédemment, la revendication « x5u » vise à faire la même chose pour les clés au format X509.

L’utilisation de l’URL d’une clé donne plus de flexibilité et des jetons de plus petite taille. Comme dans le cas de l’intégration des clés, le consommateur devra prendre quelques précautions. Il doit s’assurer que l’URL de la clé ou que les clés appartiennent à une partie de confiance. Une option est de mettre sur liste blanche toute l’URL de la clé, ou au moins le domaine d’une URL de la clé. À l’instar des clés intégrées, les clés au format X509 peuvent être authentifiées en utilisant les propriétés intégrées dans le certificat.

Notez que l’URL d’une clé indique un fichier hébergé quelque part sur un serveur. Ce fichier peut contenir plus d’une définition de clé. Pour s’assurer que le consommateur puisse identifier la bonne clé, une URL de clé est typiquement associée à la revendication « kid ». Dans ce cas, l’identifiant de la clé indique l’une des clés dans le fichier de clés.

Enfin, le consommateur peut choisir d’utiliser un mécanisme de mise en cache pour les URL de clé, pour améliorer la performance.

L’utilisation, en pratique, des JWT

Jusque-là, nous avons évoqué les propriétés techniques des jetons JWT. Toutefois, en pratique, l’utilisation des JWT requiert de faire très attention aux propriétés de sécurité d’un JWT.

Dans la plupart des cas, les JWT sont utilisés sous la forme de jetons porteurs. Un jeton porteur est un jeton qui peut être utilisé par celui qui le possède. Par conséquent, il suffit à un attaquant d’obtenir un JWT pour commencer à abuser des privilèges associés à ce jeton. Dans cette dernière partie, nous allons brièvement souligner quelques cas d’usage.

 

Les JWT dans OpenID Connect

Nous avons déjà mentionné l’utilisation de JWT dans OpenID Connect. Le fournisseur délivre un jeton d’identité au client. Le jeton d’identité contient des informations sur l’authentification de l’utilisateur avec le fournisseur. Le jeton d’identité est un jeton JWT, signé avec la clé privée du fournisseur.

OpenID Connect s’est donné beaucoup de mal pour améliorer les propriétés de sécurité du jeton d’identité. Par exemple, le protocole rend obligatoire l’utilisation des revendications « exp », « iss » et « aud ». De plus, le jeton inclut un nonce pour empêcher les attaques par répétition. En raison de ces exigences, il est difficile voire impossible d’abuser d’un jeton d’identité volé.

 

Les JWTs comme jetons d’accès OAuth 2.0

Un jeton d’accès OAuth 2.0 est un autre cas d’usage intéressant d’un JWT. Ce jeton d’accès donne au client accès à une ressource protégée, comme une API. Les jetons d’accès OAuth 2.0 sont de deux types : les jetons de référence et les jetons self-contained.

Un jeton de référence indique des métadonnées du côté du serveur, conservées par le serveur d’autorisation. Un jeton de référence fonctionne comme un identifiant, de manière semblable à l’identifiant d’une session traditionnelle.

Un jeton self-contained se présente sous la forme d’un JWT. Il contient toutes les métadonnées comme la charge utile. Pour protéger les données, l’émetteur signe le jeton en utilisant une clé privée.

Les jetons OAuth 2.0 traditionnels sont des jetons porteurs. Si l’un d’entre eux est compromis, il peut être utilisé sans restrictions par celui qui le possède. Un jeton de référence compromis peut être évoqué par le serveur d’autorisation. Il est bien plus complexe de révoquer les jetons self-contained.

Aussi, il est fortement recommandé de maintenir la durée de vie des jetons des accès aussi courte que possible. Une durée de vie de quelques minutes ou de quelques heures est assez courante pour les jetons. Il n’est pas recommandé d’avoir des durées de vie de plusieurs jours ou plusieurs mois. Si possible, les jetons d’accès de courte durée devraient être associés à des jetons de rafraichissement pour renforcer la sécurité.

 

De plus, des ajouts modernes à la spécification portent sur les propriétés du jeton porteur en introduisant des JWT de mécanismes de preuve de possession.

 

comme objets de session

Des protocoles tels que OpenID Connect et OAuth 2.0 essayent activement de combattre les faiblesses des JWT. Malheureusement, nous observons également plusieurs applications qui incorporent des JWT dans leur architecture, sans tenir compte de ces précautions.

Un exemple concret est une application utilisant des JWT pour stocker le statut d’autorisation sur le client. Cela permet d’utiliser un backend sans statut, et facilite considérablement le déploiement.

Toutefois, ce jeton côté client est un jeton porteur. Sans durée de vie courte ou mécanisme de révocation en place, ce scénario devient extrêmement vulnérable.

 

Détailler ces questions nous conduirait trop loin dans cet article. Si vous voulez en savoir plus sur ces questions, nous vous conseillons de lire « Stop using JWT for sessions » et «  Reference Tokens and Introspection.”

 

JWT Security Best Practices »

Comme vous avez pu le lire dans cet article, les JWT sont bien plus qu’une signature symétrique. La plupart des déploiements de JWT nécessitent l’utilisation de signatures asymétriques pour garantir la sécurité.

De plus, vérifier la signature d’un JWT entrant n’est que la première étape. Le consommateur doit ensuite vérifier les revendications réservées « exp » et « nef » pour s’assurer de la validité du JWT. Les revendications « iss » et « aud » doivent être vérifiées pour s’assurer que le JWT est utilisé dans le bon contexte.

Enfin, utiliser des JWT vous oblige à mettre en place une gestion de clés de chiffrement adaptée. Les JWT offrent un large éventail d’options pour gérer les clés. L’une d’elles consiste à identifier une clé en particulier utilisant un identifiant. Des options plus avancées permettent l’intégration de la clé dans le jeton, ou la récupération d’une clé à partir le l’URL d’une clé qui y est dédiée. Quel que soit le mécanisme, le consommateur doit toujours vérifier la validité de la clé avant de s’y fier.

 

Nous avons abordé beaucoup de points dans cet article. Ce Récapitulatif sur la sécurité des JWT donne un aperçu des meilleures pratiques abordées. Il vous permet d’en garder une trace pendant que vous construisez votre application. Et pour en savoir plus sur la sécurisation des API en utilisant des protocoles open standard, veuillez lire le livre blanc How to Extend Identity Security to your APIs (Comment élargir la sécurité de l’identité à vos API).

 

Partager cet article:
Ressources connexes

Lancez-vous dès Aujourd'hui

Contactez-Nous

sales@pingidentity.com

Découvrez comment Ping peut vous aider à offrir des expériences sécurisées aux employés, partenaires et clients dans un monde numérique en constante évolution.