Attaques JWT

Contexte

Contexte Les JSON Web Tokens (JWT) sont un moyen normalisé de transmission de données JSON signées cryptographiquement entre systèmes, généralement utilisés pour transmettre des "claims" ou des informations relatives aux utilisateurs dans le cadre de mécanismes d'authentification, de gestion de sessions et de contrôle d'accès. Bien qu'ils puissent contenir tout type de données, ils sont le plus souvent utilisés sur des sites web hautement distribués où les utilisateurs doivent interagir avec plusieurs serveurs backend, car toutes les informations nécessaires pour le serveur sont stockées dans le JWT lui-même, contrairement aux jetons de session classiques.

Lorsqu'un serveur émet un jeton, il génère une signature en hachant l'en-tête et la charge utile du jeton à l'aide d'une clé de signature secrète. Dans certains cas, le hachage résultant est également crypté. Ce mécanisme garantit que les données du jeton n'ont pas été altérées depuis leur émission, car toute modification de l'en-tête ou de la charge utile entraînerait une incompatibilité de signature. La signature est directement dérivée du reste du jeton, de sorte qu'un seul octet de changement dans l'en-tête ou la charge utile entraînerait une incompatibilité de signature.

Les attaques de JWT impliquent généralement qu'un attaquant envoie des jetons modifiés au serveur pour contourner l'authentification et les contrôles d'accès, et pour se faire passer pour un autre utilisateur authentifié. Ces attaques peuvent compromettre l'ensemble du site web et de ses utilisateurs, car les JWT sont couramment utilisés pour l'authentification, la gestion de sessions et le contrôle d'accès.

Les vulnérabilités des JWT découlent généralement d'une gestion défectueuse des jetons au sein de l'application, plutôt que de défauts dans la spécification des JWT elle-même. Les spécifications des JWT sont relativement flexibles par conception, ce qui permet aux développeurs web de prendre eux-mêmes des décisions d'implémentation. Cependant, cette flexibilité peut introduire involontairement des vulnérabilités, même lors de l'utilisation de bibliothèques. Par exemple, une vulnérabilité courante est de ne pas vérifier correctement la signature du jeton, ce qui permet à un attaquant de falsifier les valeurs de la charge utile du jeton. De plus, la fiabilité de la signature dépend fortement de la clé secrète restant secrète. Si la clé est divulguée ou peut être devinée, un attaquant peut générer une signature valide pour n'importe quel jeton arbitraire, compromettant tout le mécanisme.

Impact

Les attaques de JWT peuvent avoir des conséquences graves, car un attaquant capable de créer des jetons valides avec des valeurs arbitraires peut potentiellement accéder de manière non autorisée au système. Ce faisant, il peut être en mesure d'élever ses privilèges ou de se faire passer pour d'autres utilisateurs, ce qui lui permet de prendre le contrôle total de leurs comptes.

Exploitation de la faille

Les serveurs sont conçus pour ne pas stocker d'informations sur les JWT qu'ils émettent, et chaque jeton est une entité autonome. Bien que cette conception présente plusieurs avantages, elle pose également un problème fondamental - le serveur n'a aucune connaissance du contenu original du jeton ou de sa signature d'origine. Par conséquent, si le serveur ne parvient pas à vérifier correctement la signature, un attaquant peut apporter des modifications arbitraires au reste du jeton sans aucune entrave.

 {
    "username": "carlos",
    "isAdmin": false
}

Dans le cas où le serveur identifie la session en fonction du nom d'utilisateur, changer la valeur du nom d'utilisateur peut permettre à un attaquant de se faire passer pour d'autres utilisateurs connectés. De même, si la valeur isAdmin est utilisée pour le contrôle d'accès, un attaquant peut l'utiliser comme moyen simple d'escalade de privilèges.

Accepter des signatures arbitraires

Les bibliothèques JWT offrent généralement deux méthodes : une pour vérifier les jetons et une autre pour les décoder. Dans le cas de la bibliothèque jsonwebtoken pour Node.js, la méthode verify() est utilisée pour la vérification, tandis que la méthode decode() est utilisée pour le décodage. Cependant, parfois, les développeurs utilisent par erreur uniquement la méthode decode(), sans vérifier le jeton à l'aide de la méthode verify(). Cela entraîne l'absence de vérification de la signature par l'application, ce qui peut conduire à des vulnérabilités potentielles.

Accepter des JWT non signés

L'en-tête des JWT contient un paramètre "alg", qui indique au serveur l'algorithme qui a été utilisé lors de la signature.

 {
    "alg": "HS256",
    "typ": "JWT"
}

La faille réside dans le fait que la méthode du serveur pour vérifier l'authenticité du jeton peut être manipulée par un attaquant. Bien que les JWT puissent être signés à l'aide de divers algorithmes, ils peuvent également être laissés non signés, ce qui est appelé "JWT non sécurisé" lorsque le paramètre "alg" est défini sur "none". Étant donné que les jetons non signés présentent un risque important, les serveurs les rejettent généralement. Cependant, étant donné que le filtre du serveur repose sur l'analyse de chaînes, des techniques d'obscurcissement telles que la mixité des majuscules et des encodages inattendus peuvent parfois être utilisées pour contourner ces filtres.

Injection de paramètres d'en-tête JWT

Bien que la spécification JWS ne requière que le paramètre d'en-tête "alg", les en-têtes JWT, ou en-têtes JOSE, incluent généralement des paramètres supplémentaires. Les attaquants s'intéressent particulièrement à trois de ces paramètres : jwk (JSON Web Key), jku (JSON Web Key Set URL) et kid (Key ID). JWK fournit un objet JSON avec une clé intégrée, tandis que JKU fournit une URL pour que le serveur puisse récupérer un ensemble de clés contenant la clé correcte. KID fournit un identifiant que les serveurs peuvent utiliser pour identifier la clé correcte lorsque plusieurs clés sont disponibles. Ces paramètres contrôlés par l'utilisateur informent le serveur destinataire de la clé à utiliser lors de la vérification de la signature.

Injection de JWT auto-signés via le paramètre jwk

Les JWT permettent aux serveurs d'intégrer leur clé publique directement dans le jeton lui-même au format JWK.

 {
    "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
    "typ": "JWT",
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "e": "AQAB",
        "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
        "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
    }
}

Pour assurer une vérification sécurisée des JSON Web Tokens (JWT), il est recommandé aux serveurs de limiter l'utilisation des clés publiques à une liste blanche spécifique. Néanmoins, certains serveurs peuvent ne pas être correctement configurés et accepter n'importe quelle clé incluse dans le paramètre "jwk". Cela présente une vulnérabilité que les pirates informatiques peuvent exploiter en signant un JWT modifié avec leur propre clé privée RSA et en ajoutant la clé publique correspondante à l'en-tête jwk.

Injection de JWT auto-signés via le paramètre jku

Au lieu d'intégrer directement des clés publiques via le paramètre d'en-tête jwk, certains serveurs permettent l'utilisation du paramètre d'en-tête jku (JSON Web Key Set URL) pour pointer vers un ensemble JWK qui contient la clé requise. Lors de la vérification de la signature, le serveur récupère la clé pertinente à partir de l'URL spécifiée.

 {
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab",
            "n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ"
        },
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA",
            "n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw"
        }
    ]
}

Des ensembles de JWK comme celui-ci peuvent être rendus publics via un point de terminaison standard, tel que /.well-known/jwks.json.

Bien que les sites web plus sécurisés restreignent la récupération de clés aux domaines de confiance, les attaquants peuvent toujours contourner potentiellement ce filtrage en exploitant des lacunes dans l'analyse des URL.

Injection de JWT auto-signés via le paramètre kid

Pour signer divers types de données, pas seulement des JWT, les serveurs peuvent utiliser plusieurs clés cryptographiques. Pour aider le serveur à identifier la clé appropriée pour la vérification de la signature, l'en-tête JWT peut contenir un paramètre d'ID de clé (kid).

Généralement, les clés de vérification sont conservées dans un ensemble JWK, permettant au serveur de rechercher facilement le JWK correspondant au kid du jeton. Cependant, la spécification JWS ne prescrit pas de structure spécifique pour l'ID, et il s'agit simplement d'une chaîne arbitraire choisie par le développeur. Par exemple, il peut utiliser le paramètre kid pour référencer une entrée de base de données particulière ou même un nom de fichier.

Si ce paramètre est susceptible d'attaques de traversée de répertoire, un pirate informatique peut contraindre le serveur à utiliser un fichier de son système de fichiers en tant que clé de vérification, ouvrant la possibilité de graves violations de sécurité.

 {
    "kid": "../../path/to/file",
    "typ": "JWT",
    "alg": "HS256",
    "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}

Lorsqu'un serveur permet également aux JWT d'être signés à l'aide d'un algorithme symétrique, cette situation devient particulièrement risquée. Un attaquant peut manipuler le paramètre kid pour référencer un fichier statique prévisible, puis signer le JWT en utilisant un secret qui correspond au contenu de ce fichier. Bien que cela puisse potentiellement être fait avec n'importe quel fichier, l'une des façons les plus simples est d'utiliser /dev/null, un fichier qui existe sur la plupart des systèmes Linux et qui renvoie null lorsqu'il est récupéré car il est vide. Ainsi, la signature valide est produite en signant le jeton à l'aide d'un octet nul encodé en Base64.

Comment prévenir ?

Pour protéger vos sites web contre les attaques que nous avons mentionnées, mettez en œuvre les mesures de haut niveau suivantes :

Assurez-vous que vous utilisez une bibliothèque à jour pour la gestion des JWT et que vos développeurs comprennent sa fonctionnalité et toutes les implications de sécurité. Bien que les bibliothèques modernes réduisent le risque de mettre involontairement en place des mesures non sécurisées, il existe encore un potentiel de vulnérabilités en raison de la flexibilité inhérente aux spécifications. Effectuez une vérification de signature robuste sur tous les JWT reçus, en tenant compte des cas extrêmes tels que ceux signés à l'aide d'algorithmes inattendus. Appliquez une liste blanche stricte des hôtes autorisés pour l'en-tête jku pour empêcher l'accès non autorisé. Assurez-vous que le paramètre d'en-tête kid n'est pas vulnérable à la traversée de chemin ou à l'injection SQL. Bien que cela ne soit pas essentiel pour prévenir les vulnérabilités, nous recommandons d'adopter les meilleures pratiques suivantes lors de l'utilisation de JWT dans vos applications :

Définissez toujours une date d'expiration pour tous les jetons émis. Dans la mesure du possible, évitez d'envoyer des jetons dans des paramètres d'URL. Incluez la revendication d'audience (aud) (ou équivalent) pour spécifier le destinataire prévu du jeton et l'empêcher d'être utilisé sur d'autres sites web. Permettez au serveur émetteur de révoquer les jetons, par exemple lors de la déconnexion.

Developpeur et architecte passionné, qui souhaite partagé son univers et ses découvertes afin de rendre les choses plus simple pour chacun