Vol de comptes via détournement de la logique de tokens d’authentification

La plupart des applications intègrent une fonctionnalité, au demeurant critique, permettant d’identifier les utilisateurs. L’objectif :  garantir la confidentialité et l’intégrité de leurs données. Parmi les méthodes couramment utilisées pour permettre au serveur d’identifier un utilisateur, figurent les cookies de session, les tokens JWT, ou, dans certains cas, des solutions personnalisées.

De fait, la logique de génération de ces jetons de session est essentielle car, en cas de contournement ou de détournement, cela peut aboutir à un vol de compte et donc à des impacts critiques sur la sécurité des données voire à la compromission totale du système.

Dans ce write-up, nous vous présentons une situation rencontrée lors d’un audit d’application mobile qui nous a permis de comprendre, de détourner le fonctionnement des tokens et de voler des comptes utilisateurs.

Contexte : test d’intrusion en boite grise d’une application mobile interagissant avec une API

Étants en boite grise, nous disposions d’un compte utilisateur sur l’application mobile. Après capture du trafic, nous pouvions voir la requête suivante :

Requête :

GET /login HTTP/1.1
Host: 172.23.76.53:8000
TOKEN: T1P6eRxSX7DosonF7cUSD+OALbeWmMYu3DpNYrYEdtvxnrL3LoiE7SFAimZ1hrBEk4vinGO89IvycKEGR7SyoA== 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: close
Upgrade-Insecure-Requests: 1

Réponse :

HTTP/1.1 200 OK
Host: 172.23.76.53:8000
Date: Tue, 12 Sep 2023 13:40:30 GMT
Connection: close
X-Powered-By: PHP/8.2.8
Content-type: text/html; charset=UTF-8

Welcome User

Cette requête de connexion est la toute première de l’application. Cela signifie que le mot de passe est surement dans le token, qui, par ailleurs est en base 64.

Notre premier objectif est donc d’essayer de le décoder. Pour ce faire, nous utilisons le site CyberChef :

Comme nous pouvons le voir, le token ne se limite pas à un simple encodage en base64. En effet, les données contenues dans ce dernier sont également chiffrées, ce qui signifie qu’il faut chercher une méthode pour accéder aux informations permettant le déchiffrement.

Heureusement pour nous : qui dit application mobile dit code source !  Et comme aucune requête préalable n’a été envoyée, il est fort probable que toutes ces informations soient embarquées dans l’application mobile.

Nous essayons donc de récupérer le code source.

Décompilation de l’application mobile

En fonction des technologies utilisées (native, Flutter, etc.), il existe plusieurs méthodes pour récupérer le code source d’une application mobile.

Dans notre cas, l’application était en Xamarin. Cela signifie que le code mobile est écrit en .NET. Pour récupérer le code source, nous allons procéder en plusieurs étapes :

  • Décompiler l’application avec apktool : apktool d app.apk
  • Déballer les fichiers Xamarin : pyxamstore unpack -d app/unknown/assemblies/
  • Utiliser un décompilateur .NET, dans notre cas dotPeek de JetBrains.

Cela nous permet ensuite de naviguer dans le code décompilé et donc de pouvoir l’analyser.

Découverte du chiffrement utilisé (AES-256-CBC)

Dans ce contexte, notre objectif était de rechercher dans le code source comment l’en-tête « TOKEN » était généré. Pour ce faire, nous avons effectué une recherche dans le code décompilé en utilisant le mot-clé « token » afin d’identifier le processus de génération.

Et cela nous a permis de savoir que la donnée du token était chiffrée, avant d’être encodée en base64, avec l’algorithme AES-256-CBC.

  • Nous avons un chiffrement symétrique, donc si nous savons chiffrer nous savons déchiffrer et inversement.
  • Avec ce mode de chiffrement, nous devons connaitre la passphrase et le vecteur d’initialisation (IV).

Pour résumer, voici une image représentant la méthode de chiffrement des données avec AES et le mode CBC :

Source : Wikimedia

Comme indiqué précédemment, l’application chiffre directement les données. De fait, elle embarque aussi la passphrase et le vecteur d’initialisation (IV) :

iv = 8c7b7ef51795142327fe06cea699e39b (sous forme hexadécimal)
passphrase = "AlfredWillFindMeaGoodPassphrase!"

Ainsi, nous pouvons déchiffrer le token avec toutes ces informations :

Et obtenir le token suivant :

[email protected]|password=superpassword1234|App=company1

Maintenant imaginons que l’utilisateur veuille cibler un administrateur. Il dispose pratiquement de toutes les informations sauf du mot de passe, ce qui entrave la possibilité de forger un token admin.

Par ailleurs, rappelons que l’inclusion du mot de passe dans le token est une mauvaise pratique de sécurité, car cela facilite grandement la tâche aux attaquants en cas de fuite. De plus, nous pouvons apercevoir que le token ne dispose pas d’une durée de validité. En cas de fuite, il sera donc toujours valide.

Ainsi avec cette vulnérabilité, il n’était pas possible de voler un compte. On aurait pu s’arrêter ici, mais continuons notre investigation.

Identification d’un token prédictible

En effet, nous nous sommes rendu compte qu’après la connexion, l’application utilisait un jeton différent :

Version chiffrée :

T1P6eRxSX7DosonF7cUSD1jxPCZrgA0tcnW+3wfxhX9aRuxSURkzQ39W1PH2K81icOoCRqR3jXj0tkQcjjArfOVzE2wahVpQu40xoobj1j1ToQ84fDq82sQA/8mxQd06KX+RoiW3FbNoTtUfON54qbmtptE9e2NtWAv2IX++FTmzlL0uPeF9G5X/wtoKTp/UEoyP7OtWuc97Pt832pIqPtnMvebL6k2S/CDa590XbIw=

Version déchiffrée :

[email protected]|App=company1|hash=250ec51456a3c45e4a8b55344fffaf188c16180823efb813130c1672405308042eaded1146cbc1705635cddf386596834e17d67ebdcafe1a20d8700011f7220a

Nous remarquons ici que le mot de passe n’est pas directement marqué en clair dans le token car remplacé par un hash.

En regardant la taille, nous pouvons supposer que celui-ci est un sha512. Cependant, il reste encore à connaître les données à lui fournir pour avoir le hash valide. Dans ce cas de figure, nous regardons de nouveau dans le code source et on s’aperçoit que le hash est de la forme sha512(email+’#’+company). C’est-à-dire que le hash vaut sha512([email protected]#company1).

Maintenant, admettons que nous voulons avoir accès au compte de l’admin. Nous allons donc commencer par générer le hash :

[email protected]#company1  => f765f0945a56a49e6f245eb74fe312a2a6619f7cdfe2298bf8bc4d816060583704ebb5bf87bcdd02218d699c0934f78b7044ba475432d5f6a4eb135348ad43a7

Puis nous le chiffrons :

7p/fKUgE5tiG7J+4MMeAsvaw66KNur4NpskFiwAnxqbqvvuElIwusapyaN7V1MOMG/YSeY+3OZ/RzunzUUKWRMI+y8XCL1bX0lJQtJYaWkey9LQBBy158M6Dq3SbglAqes+B+dG18vf6urXISZZAONkR7FxoDbFekqVgZ4urzQBBzkjkRvXJlAVkuhQ879OgplBSNSN382d4Qnrufeb6wqJWp0tFp/dj+BhsbkKsQfU=

Et si nous soumettons la requête avec ce jeton, nous voyons que cela fonctionne :

Requête :

GET /login HTTP/1.1
Host: 172.17.42.3:8000
Token: 7p/fKUgE5tiG7J+4MMeAsvaw66KNur4NpskFiwAnxqbqvvuElIwusapyaN7V1MOMG/YSeY+3OZ/RzunzUUKWRMI+y8XCL1bX0lJQtJYaWkey9LQBBy158M6Dq3SbglAqes+B+dG18vf6urXISZZAONkR7FxoDbFekqVgZ4urzQBBzkjkRvXJlAVkuhQ879OgplBSNSN382d4Qnrufeb6wqJWp0tFp/dj+BhsbkKsQfU=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

Réponse :

HTTP/1.1 200 OK
Host: 172.17.42.3:8000
Date: Thu, 17 Aug 2023 07:57:36 GMT
Connection: close
Content-type: text/html; charset=UTF-8

Welcome Admin

Ainsi, grâce au code source de l’application mobile, nous avons pu forger un token valide et se faire passer pour un admin en connaissant uniquement son email.

Ici, nous étions dans des conditions de tests boite grise pour comprendre le fonctionnement de l’application en partant d’un token valide (celui d’un utilisateur).

Exploitation de cette vulnérabilité en boite noire

Cette attaque aurait également pu fonctionner en boite noire. Ci-dessous les différentes étapes :

  • Pour commencer, on saisit des données invalides dans le formulaire de connexion.
  • Nous voyons que le token est envoyé dans les headers.
  • Nous cherchons dans le code comment le déchiffrer.
  • Nous trouvons les informations nécessaires au déchiffrent et par conséquent au chiffrement.
  • Nous nous rendons compte aussi que l’application possède deux tokens et pas un seul.

Les deux prérequis dans cette attaque sont donc la connaissance du nom de l’application, et d’un email. De fait, cette attaque fonctionnera mieux de manière ciblée même s’il est également possible de trouver le nom de l’application (via brute force) et de tester avec une liste d’email existante ou de générer une liste d’email potentiellement valide (suite à une recon par exemple).

Par ailleurs, cette attaque est plus impactante en boite grise, car la solution était déployée pour plusieurs clients sur des serveurs différents avec, cependant, le token utilisé de la même façon. Cela signifie donc qu’en connaissant un serveur hébergeant l’application et un email valide, il est possible de compromettre aussi une autre organisation cliente.

Conclusion

L’éditeur de la solution testée avait choisi une approche multi-tenant avec une base de données par organisation. Cependant, la segmentation n’est pas suffisante car il était possible de forger un token valide pour tous les tenants.

En effet, tous les tenants possédaient la même passphrase et le même vecteur d’initialisation. Néanmoins, il fallait disposer d’informations complémentaires comme le nom de l’application cible. Cela rendait l’exploitation moins triviale, mais pas impossible.

Par ailleurs, dans le cas présenté ici, nous pouvons noter que le jeton n’expirait jamais. En cas de fuite de ce dernier, un attaquant pouvait le réutiliser indéfiniment sans connaitre la logique de génération.

La base de cette vulnérabilité est que la solution s’appuyait uniquement sur une sécurité coté client, ce qui est à proscrire pour gérer l’authentification. Une de nos recommandations a donc été de s’appuyer sur le serveur pour fournir le token d’authentification.

Enfin, un petit conseil de pentester : ne réinventez jamais la roue ! En effet, il existe des solutions communes et éprouvées telles que les tokens JWT permettant d’assurer facilement et de manière sécurisée (si bien implémentées) l’authentification et la gestion des sessions sur les applications.

Auteur : Thomas DELFINO – Pentester @Vaadata