Race condition : principes, exploitations et bonnes pratiques sécurité

Avec une bonne connexion Internet et un matériel performant, un utilisateur peut avoir l’impression que ses actions sur une page web sont instantanées ou presque.

Cependant, il ne faut pas oublier qu’un serveur met du temps à traiter des requêtes. Même s’il s’agit de millisecondes, ce délai peut intéresser un attaquant. On parlera alors d’attaques de type race condition.

Principes, techniques d’exploitations, nous vous présentons dans cet article une vue d’ensemble des race conditions, ainsi que les bonnes pratiques sécurité et mesures à implémenter pour contrer les risques d’attaque.

Qu’est-ce qu’une race condition ?

Les attaques de type « race condition » exploitent le timing de l’envoi simultané de plusieurs requêtes.

L’objectif est de tirer parti du fait que les serveurs peuvent gérer plusieurs threads, c’est à dire traiter des requêtes en parallèle. En envoyant des requêtes simultanées qui tentent de modifier la même donnée, l’attaquant cherche à provoquer un état inattendu du serveur, non prévu dans son fonctionnement normal.

Ces attaques surviennent souvent en raison d’un décalage de temps entre le moment où l’application vérifie une condition de sécurité et celui où elle modifie l’état d’un objet.

Ce type de vulnérabilité est appelé une faille TOCTOU (Time Of Check, Time Of Use).

Prenons l’exemple d’un site e-commerce qui permet de poster un avis sur un produit uniquement après l’avoir acheté, et ce, une seule fois.

Lorsque l’utilisateur envoie sa requête pour poster un avis, le serveur commence par vérifier qu’aucun autre avis n’a déjà été publié par cet utilisateur.

Si la condition est respectée, le commentaire est enregistré et le nombre d’avis de cet utilisateur est mis à jour à 1, l’empêchant ainsi d’en poster davantage. Cependant, ce processus ne se fait pas instantanément, mais en l’espace de quelques millisecondes.

Un attaquant pourrait profiter de ce court délai pour envoyer plusieurs requêtes avant que la variable ne soit mise à jour à 1, lui permettant ainsi de poster plusieurs avis.

Comment exploiter une vulnérabilité de type race condition ?

Cette catégorie de vulnérabilités est généralement difficile à exploiter pour plusieurs raisons. Une connaissance approfondie de l’application est souvent nécessaire, et des outils spécialisés sont requis pour réussir l’exploitation.

De plus, un facteur externe à prendre en compte est l’état du réseau sur lequel les requêtes sont envoyées. Un simple ralentissement lors de l’envoi d’une requête peut suffire à faire échouer l’attaque.

La latence du serveur peut également influencer les tentatives de l’attaquant, mais cet élément échappe à son contrôle et est souvent négligeable comparé à la latence du réseau.

Il est également important de comprendre que toutes les tentatives ne réussissent pas. Il peut être nécessaire de faire plusieurs essais avant d’obtenir un résultat concluant.

Des recherches ont été menées pour trouver des méthodes permettant de contourner les aléas du réseau. L’une des premières techniques, appelée « last-byte sync », s’appuyait sur le protocole HTTP/1.1.

En effet, les serveurs web ne traitent les données reçues qu’une fois tous les octets de la requête entièrement transmis. L’idée est donc d’envoyer plusieurs requêtes sans leur dernier octet, puis de les terminer en envoyant ce dernier octet en parallèle.

Ainsi, la latence du réseau a moins d’impact, car seuls quelques octets sont transmis à la fin.

Cette technique a été améliorée avec l’attaque « single-packet », qui utilise le protocole HTTP/2. Ce protocole offre une fonctionnalité appelée multiplexing, permettant d’envoyer deux requêtes HTTP complètes dans un même paquet TCP.

Toutefois, l’envoi de seulement deux requêtes peut ne pas suffire, ce qui amène à utiliser également la technique du « last-byte sync » pour envoyer le dernier octet à la fin.

Les résultats obtenus avec cette méthode sont assez efficaces. Elle permet en effet d’envoyer jusqu’à 20 requêtes en parallèle, toutes arrivant au serveur dans un délai d’environ 1 milliseconde.

L’outil de pentest Burp intègre ces deux techniques, rendant leur utilisation très accessible.

Dans l’onglet « Repeater », il faut créer un groupe de requêtes.

Il existe plusieurs options pour envoyer un groupe de requêtes. On peut choisir de les envoyer via une ou plusieurs connexions, ou en parallèle en utilisant la technique de synchronisation du dernier octet.

Exemple d’exploitation d’une race condition

Voyons un exemple d’exploitation d’une race condition rencontré lors d’un pentest.

Le temps de réponse d’une application peut fournir des informations sur ses données, même si la réponse semble identique. Un exemple courant concerne la fonctionnalité de réinitialisation de mot de passe sur les sites avec un système d’authentification.

Une première requête est envoyée pour demander un email de réinitialisation pour un compte existant. Une deuxième requête est faite pour un compte inexistant. Très souvent, une différence notable dans le temps de réponse est observée, parfois de l’ordre d’une seconde. Cela s’explique par le temps nécessaire au serveur pour envoyer l’email. En revanche, aucun email n’est envoyé pour un compte invalide, ce qui réduit le temps de calcul.

On pourra alors tenter d’énumérer des comptes valides avec une liste d’emails. Il suffira de regarder le temps de réponse de l’application à la recherche de requêtes plus longues que les autres.

Si la différence de temps est moindre, la technique du single-packet attack (présenté précédemment) peut être utilisée pour quasiment supprimer la latence provoquée par le réseau.

Cette fuite d’informations n’est pas très impactante sur la plupart des plateformes à moins qu’elle soit très sensible et que l’éditeur ne veuille absolument pas que l’on sache qui est inscrit dessus.

Par ailleurs, les plateformes e-commerce sont particulièrement vulnérables aux race conditions, avec des conséquences bien plus importantes que simplement découvrir un email valide. Ces failles se trouvent souvent au niveau du paiement et de l’utilisation des coupons de réduction.

Lors d’un audit, nous avons tenté d’appliquer plusieurs fois le même code de réduction pour réduire significativement le montant du panier, sans succès. Cependant, il a été possible d’appliquer deux coupons différents sur un même panier, alors que normalement, ces coupons ne pouvaient pas être cumulés.

Lorsque nous avons tenté d’appliquer ces deux coupons de manière successive, le second n’a pas été pris en compte dans le panier.

Requête :

POST /redeem_coupon HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0

Coupon=Holidays20

Réponse :

HTTP/1.1 200 OK
Date: Wed, 02 Oct 2024 15:17:50 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=prtcdvcds93vrad85nqfhk7fl3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: shop.php
Content-Length: 1127

Coupon applied.

Requête :

POST /redeem_coupon HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0

Coupon=Christmas10

Réponse :

HTTP/1.1 500 Internal servor error 
Date: Wed, 02 Oct 2024 15:17:50 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=prtcdvcds93vrad85nqfhk7fl3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: shop.php
Content-Length: 1127

One coupon already applied.

Si l’on supprime le coupon déjà appliqué, et que l’on envoie les 2 requêtes précédentes dans un temps très court (toujours avec la technique du single-packet attack) l’attaque fonctionne. Les 2 coupons sont alors appliqués simultanément.

Ces requêtes modifient la base de données dans un laps de temps très court. Le serveur contrôle qu’aucun coupon n’est appliqué sur le panier. Cependant, lorsque la seconde requête est traitée, la variable comptant le nombre de coupons appliqué n’a pas encore eu le temps d’être incrémentée.

Comment prévenir les attaques race condition ?

Plusieurs approches peuvent être envisagées pour prévenir des race conditions :

  • Éviter l’utilisation de multiples stockages de données, ce qui pourrait entraîner des problèmes de cohérence.
  • Les fonctions sensibles de l’application doivent être effectuées avec des requêtes atomiques au niveau de la base de données. Ces systèmes gèrent la concurrence des requêtes.
  • L’utilisation d’un principe de programmation présent dans de nombreux langages est conseillée : le mutex. Cela permet d’éviter que des ressources partagées ne soient modifiées simultanément. D’autres concepts similaires peuvent être employés : les semaphores et les monitors.
  • S’assurer que les fonctions critiques sont thread-safe lors du développement; c’est-à-dire qu’elles puissent être utilisées par plusieurs threads sans risque de conflits.

Auteur : Julien BRACON – Pentester @Vaadata