2 avril 2021

La race-condition, un ennemi redoutable, méconnu des développeurs

Introduction

Il est de ces vulnérabilités lointaines, où les risques sont abstraits et les mitigations obscures. Les race-conditions en font partie. Originellement, la situation de concurrence (race-condition) est une situation caractérisée par un résultat différent selon l’ordre dans lequel agissent les acteurs du système. Le terme est plutôt employé à propos de programmes informatiques et de systèmes électroniques. C’est généralement considéré comme un défaut car source de panne ou de blocage.

Bien souvent méconnues des développeurs, ces situations de concurrence sont fréquemment sources de bugs, et à l’origine d’attaques sophistiquées exploitants les défauts de logique de ces derniers.

Les situations de concurrence sont une catégorie de vulnérabilités dans laquelle deux agents (deux scripts, deux services, etc) sont en compétition pour une ressource, et parce que le timing est trop juste entre deux actions de ces agents, le résultat est différent de celui attendu. L’un des exploits de race-condition les plus célèbres est Dirty Cow, où elle permet de modifier un fichier qui n’est pas censé être modifiable, donnant ainsi les droits « root » à l’utilisateur.

Preuve de concept

Afin de comprendre précisément le problème, nous allons mettre en place une preuve de concept simple pointant du doigt l’exploitation du bug de logique.

Admettons la table SQL suivante :

Figure 1 : Table SQL avec une colonne ID et dernière date d’accès

Elle est simplement composée de deux colonnes (Id et date_modified). La première colonne est l’identifiant auto-incrémenté de chaque entrée. La seconde colonne correspond à la date du dernier accès à l’élément dans la base (en effet, à chaque fois que nous allons récupérer une entrée, nous allons la mettre à jour, et par conséquent mettre la date à l’heure. Dans cet exemple, il n’y a rien à mettre à jour, mais nous pouvons imaginer une table similaire avec des colonnes dynamiques).

Le script PHP suivant consiste à récupérer l’entrée la plus ancienne, puis mettre à jour sa date de modification (de sorte à ce que la prochaine entrée la plus ancienne ne soit pas la même) :

Figure 2 : Script PHP affichant l’entrée la plus ancienne dans la base de données

Le fichier Bash ci-dessous se divise en deux parties :

  • Une première partie sans exploitation de la race-condition, où chaque requête sera exécutée après la précédente ;
  • Une seconde partie avec exploitation de la race-condition, où toutes les requêtes sont exécutées parallèlement.
Figure 3 : Script Bash simulant des requetes avec et sans race condition

Le résultat peut malheureusement déstabiliser beaucoup de développeurs non sensibilisés à cette vulnérabilité :

Figure 4 : Output du POC en bash avec et sans race condition

Sans l’exploitation de la race-condition, l’ordre est : 2, 1 puis 3. En effet, si nous reprenons la table ci-dessus, la date la plus ancienne correspond à l’entrée n°2 (2021-03-30 16:33:51). Vient ensuite la première entrée (2021-03-30 16:33:52), puis la troisième (2021-03-30 16:33:54).

Avec l’exploitation de la race-condition, on se rend compte que le script va procéder plusieurs fois (toutes les fois, pour être précis) à l’ID n°1. L’exploitation de cette race-condition s’aperçoit également au niveau des états de la table. En effet, le date_modified est devenu exactement le même :

Figure 5 : Table mise à jour suite à la race condition

Ainsi, il est possible d’imaginer de nombreux scénarios où ce bug devient une vulnérabilité : contourner des limites en envoyant les requêtes simultanément est par exemple réalisable.

Actualité

En février 2021, un chercheur du nom de Laxman Muthiyah a réussi à exploiter cette vulnérabilité sur le système d’authentification de Microsoft. En utilisant la fonctionnalité de récupération de mot de passe et en testant simultanément tous les codes de récupération, il a réussi à obtenir un accès à un compte de manière illégitime. Microsoft l’a récompensé de 50 000 dollars :

Figure 6 : Race condition menant à une vulnérabilité critique de type account takeover

Le lien vers son article est disponible ici : https://thezerohack.com/how-i-might-have-hacked-any-microsoft-account

En 2019, il avait déjà réussi à exploiter ce même bug sur le système de récupération de mot de passe d’Instagram :

Figure 7 : Race condition menant à une vulnérabilité critique de type account takeover sur Instagram

Le lien vers son article est disponible ici : https://thezerohack.com/hack-any-instagram

Conclusion et remédiation

Pour conclure, ces situations de concurrence sont bien souvent ignorées par les développeurs, se traduisant par un risque d’exploitation très fort.

Pour mitiger le risque et corriger ce bug, il est nécessaire de développer des fonctionnalités dites : « thread-safe » par le biais de plusieurs mécanismes (via des mutex).

Concernant les requêtes SQL, il est fondamental que la colonne utilisée pour trier (la condition WHERE) soit toujours unique. Dans notre cas, la granularité de date_modified est insuffisante. En effet, il est possible que plusieurs entrées finissent par avoir la même valeur de date_modified, ce qui cause cette incohérence au niveau du tri :

Figure 8 : Table SQL avec une colonne ID et dernière date d’accès

La solution est de ne jamais trier sur le temps et d’implémenter des verrous aux requêtes SQL (via les commandes LOCK TABLES et UNLOCK TABLES).

S’il n’y a pas d’autres solutions que de trier sur le temps, il est impératif de trier sur la plus petite granularité temporelle possible. Dans notre cas, nous avons rajouté une colonne microtime_updated :

Figure 9 : Table SQL avec ajout de la colonne microtime_modified

Dans notre script PHP, nous allons mettre à jour les lignes suivantes, afin d’ajouter un verrou sur notre table, ainsi que le tri sur une meilleure granularité :

Figure 10 : Script PHP mis à jour

En exécutant notre POC, nous nous rendons compte que la race_condition a été corrigée :

Figure 11 : Output du POC en bash avec et sans race condition

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *