À la recherche de l'UUID gagnant – Exploitation d'un UUID en version 1 menant à la compromission des comptes de l’application 

Introduction 

Les applications Web implémentent différents types de fonctionnalités faisant intervenir des éléments indexables (utilisateurs, rôles, articles, etc.). 

Un exemple concret serait celui d’un utilisateur indexé par l’identifiant `id=3` souhaitant accéder à ses données, qui pourrait requêter la page concernée en y spécifiant son id : `/user?id=3`. Cependant, sans sécurité adéquate, un attaquant pourrait trivialement itérer sur ces identifiants incrémentaux afin de récupérer les données des autres utilisateurs. 

Les défauts de contrôle d’accès, intégrant les références directes non sécurisées (IDOR) telles qu’expliquées ci-dessus, sont passés de la 5ème à la 1ère place au classement des vulnérabilités de l’OWASP (Top 10). La popularité, la fréquence d’apparition ainsi que la simplicité d’exploitation de ce type de vulnérabilité ont mis en avant la nécessité de durcir les contrôles d’accès. 

Bien que le contrôle d’accès côté serveur se basant sur l’objet dédié à l’authentification et à la gestion de la session d’un utilisateur (cookie, JWT, etc.) soit la barrière principale contre l’accès illégitime aux données d’un autre utilisateur, un attaquant aura sensiblement plus de difficultés à deviner ou prédire si les ressources sont indexées par des UUID (pour Unique Universal Identifier) plutôt que par des identifiants incrémentaux. Il existe aujourd’hui plusieurs types d’UUID, alors comment savoir lequel utiliser ? 

Cet article détaille l’exploitation d’un UUID en version 1 dans le cadre d’une fonctionnalité de réinitialisation de mot de passe, ayant permis de définir de nouveaux mots de passe et d’ainsi compromettre l’intégralité des comptes applicatifs. 

Types d’UUID 

Les UUID sont des chaînes de 32 caractères hexadécimaux séparés en 5 groupes par des traits d’union, par exemple : eda10ec2-7779-48d5-b1c1-cddbf0b6965e. Il en existe aujourd’hui 5 versions, qui ne seront pas toutes évoquées dans le cadre de cet article. Pour plus de détails sur ces 5 alternatives, la RFC 4122 ou l’article du site uuidtools sont respectivement accessibles aux adresses suivantes : 

Ce qu’il faut retenir, c’est que les UUID sont préférés aux identifiants incrémentaux pour leurs caractères aléatoires, uniques, complexes et non-prédictibles (ou presque, comme on le verra dans la section dédiée à l’exploitation). 

Quelques notions globales sur les UUID pouvant être utiles lors de pentests : 

  • Le 13ème caractère hexadécimal d’un UUID (soit le 1er du 3ème groupe) indique sa version ; 
  • Les premiers octets d’UUID en version 1 et en version 2 sont basés sur le timestamp auxquels ils sont générés ; 
  • Les UUID en version 3 et 5 sont respectivement générés à partir des algorithmes de hachage MD5 et SHA1 (tronqué) et ne sont pas dépendants du timestamp ; 
  • Les UUID en version 4 disposent de 31 caractères hexadécimaux totalement aléatoires (le 32ème caractère étant un 4 situé en 13ème position, pour indiquer sa version). 

Focus sur l’UUID version 1 

Voici la structure d’un UUID en version 1 : 

Schéma serveur

Source : https://medium.com/teads-engineering/generating-uuids-at-scale-on-the-web-2877f529d2a2 

Ce type d’UUID se base sur 4 données différentes : 

  • Le timestamp auquel il est généré (15 caractères hexadécimaux) ; 
  • La “clock sequence”, une donnée aléatoire fixée qui commence toujours par 8, 9, A ou B (4 caractères hexadécimaux) ; 
  • Le “node”, qui correspond à l’adresse MAC du système sous-jacent ou à une adresse MAC aléatoire mais également fixée pour l’implémentation du générateur d’UUID (12 caractères hexadécimaux). Les adresses MAC étant uniques à l’ordinateur qui les génère (en théorie), en incluant une adresse MAC dans l’UUID, on peut s’assurer (en théorie toujours) que deux ordinateurs différents ne généreront jamais le même UUID. 

Mise en situation d’une exploitation d’un défaut d’UUID 

Lors d’un pentest, nous avons rencontré une fonctionnalité de réinitialisation de mot de passe plutôt classique et l’avons analysée à partir d’un compte utilisateur mis à notre disposition : 

Dans ce mail de réinitialisation de mot de passe, nous avons constaté l’utilisation d’un jeton qui, à première vue, semble relativement complexe et robuste : 

À ce stade, on peut imaginer qu’une telle donnée n’est pas exploitable, de par sa complexité apparente de 1632, soit 2128 possibilités. Néanmoins, en générant plusieurs demandes de réinitialisation de mot de passe à la suite, nous avons pu comparer les jetons renvoyés par l’application et remarqué d’importantes similarités dans ces derniers, et notamment un caractère « time-based »: 

Valeur du jeton Extraction du timestamp auquel il a été généré 
051ee4a0-98c6-11ec-ab1c-f79620029b7a 2022-02-28 18:41:19.850000.0 UTC 
0b15fe20-98c6-11ec-ab1c-f79620029b7a 2022-02-28 18:41:29.858000.0 UTC 
0c2c7730-98c6-11ec-ab1c-f79620029b7a 2022-02-28 18:41:31.683000.0 UTC 
0d0c01c0-98c6-11ec-ab1c-f79620029b7a 2022-02-28 18:41:33.148000.0 UTC 

Si l’on applique ce que l’on a dit plus haut, on peut tout de suite identifier qu’il s’agit d’UUID en version 1 : 051ee4a0-98c6-11ec-ab1c-f79620029b7a. 

De plus, en creusant, nous avons découvert que cette version était bel et bien générée à partir d’un timestamp. Des extensions Burp peuvent le faire automatiquement pour vous comme l’extension UUID Detector (disponible à l’adresse https://portswigger.net/bappstore/65f32f209a72480ea5f1a0dac4f38248) permettant en plus d’extraire d’autres informations techniques comme : 

  • La clock sequence ; 
  • Le node ; 
  • Le timestamp. 

De là, on peut d’ores et déjà reconsidérer la complexité du jeton : 

  • La clock sequence et le node ne varient pas, ce qui fixe les 16 derniers caractères de l’UUID ; 
  • De même, la version de l’UUID, indiquée en 13ème position, reste la même. 

On passe donc de 1632 (2128) à 1615 (260) possibilités, ce qui reste difficilement exploitable dans le cadre d’attaques par recherche exhaustive (bruteforce) dans le temps imparti à l’audit. 

Néanmoins, un scénario d’attaque commence alors à se dessiner : si on effectue nous-mêmes une demande de réinitialisation de mot de passe d’un compte, par exemple celui de l’administrateur de la solution, l’on peut tenter d’obtenir l’UUID associé afin de changer son mot de passe. 

Le dernier élément de l’UUID de l’utilisateur ciblé dont nous ne disposons pas (encore) est le timestamp utilisé lors de sa génération. Cependant, si on se replace dans le contexte de l’application et si l’UUID est généré lors de la réinitialisation de mot de passe, on peut tout à fait récupérer les 2 timestamps suivants : 

  • Le timestamp correspondant à notre requête HTTP ; 
  • Le timestamp correspondant à la réponse HTTP. 

Ces 2 timestamps nous permettent de créer une période hypothétique dans laquelle le timestamp clé se trouve. 

L’UUID gagnant découlera de la génération de tous les UUID correspondant à la période de temps évoquée. Nous pouvons donc être en mesure de réduire la complexité de 2128 possibilités à “n” possibilités, où “n” est le nombre de millisecondes s’étant écoulées entre la requête et la réponse HTTP. 

Pour conclure cette exploitation, nous avons été en mesure de dérouler le scénario d’attaque suivant afin de compromettre le compte de l’administrateur de la plateforme à l’aide d’un script implémentant la logique précédemment évoquée. 

Dans notre cas, l’UUID gagnant pouvait se retrouver en se basant sur une différence dans la réponse HTTP de l’application suite à l’envoi de l’UUID et du nouveau mot de passe. Néanmoins, l’exploitation aurait également pu se dérouler en blind sans examiner la réponse HTTP, mais en tentant de se connecter directement avec le nouveau mot de passe défini. 

Pôle Audit XMCO

Adrien Guinault

Découvrir d'autres articles