Race conditions¶
Les conditions de course (race conditions) sont une sous-famille des failles de logique métier. Elles apparaissent quand un site traite plusieurs requêtes simultanément sans protection : plusieurs fils d'exécution interagissent avec la même donnée en même temps, créant une collision au comportement imprévisible. Une attaque consiste à chronométrer des requêtes pour provoquer délibérément ces collisions. La période pendant laquelle la collision est possible — la fenêtre de course — n'est souvent qu'une fraction de seconde.
Verrouillage trompeur
Certains frameworks verrouillent les requêtes pour éviter la corruption de données — le gestionnaire de session natif de PHP ne traite qu'une requête par session à la fois. Ce comportement peut masquer une faille triviale. Si les requêtes semblent traitées séquentiellement, il faut réessayer avec un jeton de session différent pour chacune.
Dépassement de limite¶
La race condition la plus connue permet de dépasser une limite imposée par la logique. Considérons un code promotionnel à usage unique : l'application vérifie qu'il n'est pas déjà utilisé, applique la réduction, puis met à jour la base. En envoyant plusieurs requêtes exactement au même moment, la base peut ne pas encore refléter l'utilisation au moment des vérifications — la réduction s'applique plusieurs fois.
Ce dépassement de limite (sous-type des failles TOCTOU, time-of-check to time-of-use) se décline en de nombreuses variantes : réutiliser un code promo, noter un produit plusieurs fois, retirer plus d'argent que le solde, réutiliser un CAPTCHA, contourner une limite de débit pour brute-forcer.
Détection et exploitation. On identifie un endpoint à usage unique ou limité ayant un impact, puis on lui envoie plusieurs requêtes très rapidement pour voir si on le dépasse. La difficulté principale est de synchroniser les requêtes pour qu'au moins deux fenêtres s'alignent. Burp offre plusieurs moyens : grouper des onglets dans Repeater et les envoyer en parallèle, utiliser l'action « trigger race condition », ou recourir à Turbo Intruder.
Turbo Intruder¶
Repeater envoie des attaques « en un seul paquet », mais Turbo Intruder va plus loin (au prix de quelques compétences Python) : nouvelles tentatives, délais précis, très grand nombre de requêtes. Pour l'attaque à paquet unique — incompatible avec HTTP/1, donc à réserver aux cibles HTTP/2 — on configure engine=Engine.BURP2 et concurrentConnections=1, on met les requêtes en file dans une même « porte » (gate), puis on l'ouvre pour tout envoyer en parallèle :
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2)
for i in range(20):
engine.queue(target.req, gate='1')
engine.openGate('1')
Le modèle race-single-packet-attack.py fourni avec Turbo Intruder sert de point de départ. Avec une liste de mots (mots de passe, par exemple), la valeur à faire varier est marquée %s dans la requête et la liste copiée dans le presse-papiers.
Séquences multi-étapes cachées¶
Une seule requête peut déclencher en arrière-plan une séquence entière, faisant passer l'application par plusieurs sous-états avant la fin du traitement. En identifiant des requêtes qui interagissent avec la même donnée, on peut abuser de ces sous-états pour exposer des variations sensibles au timing, bien au-delà du simple dépassement de limite — par exemple contourner une authentification multifacteur en effectuant la première étape puis en forçant directement la navigation.
Recherche d'indices. On envoie la séquence en separate connections puis en parallèle ; un comportement différent est un premier indice. Il faut ensuite comprendre le mécanisme, retirer les requêtes superflues, et confirmer la reproductibilité. Mieux vaut considérer les race conditions comme des faiblesses structurelles que comme des failles isolées.
Race conditions multi-endpoints¶
Ce type envoie des requêtes à plusieurs endpoints en même temps. Exemple : sur une boutique, si la validation du paiement et la confirmation de commande s'exécutent pendant le traitement d'une requête, on peut ajouter des articles au panier entre les deux. Aligner les fenêtres est délicat à cause de deux délais : celui de l'architecture réseau (établissement de connexion backend) et celui du traitement propre à chaque endpoint.
Deux techniques de contournement. Le réchauffement de connexion (connection warming) consiste à envoyer une ou deux requêtes anodines en début de groupe pour lisser le délai réseau ; si la première requête reste lente mais que le reste est traité dans une fenêtre courte, on peut ignorer ce délai. Si un endpoint reste irrégulier même en paquet unique, c'est que le délai backend interfère — on peut alors abuser d'une limite de débit : envoyer un grand nombre de requêtes tampons pour déclencher volontairement le ralentissement côté serveur et synchroniser ainsi l'attaque.
Race conditions à endpoint unique¶
Envoyer des requêtes parallèles avec des valeurs différentes peut déclencher une collision puissante. Exemple : un système de réinitialisation de mot de passe qui stocke l'identifiant et le jeton dans la session. Deux demandes en parallèle depuis la même session, avec deux noms d'utilisateur différents, peuvent produire une collision où l'identifiant est celui de la victime mais le jeton celui de l'attaquant — qui le reçoit. Les opérations par e-mail sont de bonnes cibles, l'envoi se faisant souvent dans un fil d'arrière-plan après la réponse HTTP.
Construction partielle d'objet¶
De nombreuses applications créent un objet en plusieurs étapes, laissant une fenêtre où l'objet est partiellement initialisé. Si l'inscription crée l'utilisateur puis définit sa clé d'API en deux requêtes SQL, il existe un instant où l'utilisateur existe sans clé. On exploite cela en injectant une valeur qui correspond à la valeur non initialisée (chaîne vide, null). Les frameworks permettant d'envoyer des structures non-chaînes via une syntaxe particulière, on force une comparaison entre notre null et la valeur non initialisée :
En PHP, param[]= produit un tableau vide ; en Ruby on Rails, param[key] produit {"key"=>nil}. Avec un mot de passe (souvent haché), il faudrait en revanche injecter une valeur correspondant au hachage de la valeur non initialisée.
Attaques sensibles au timing¶
Même sans race condition, l'envoi de requêtes au timing précis révèle parfois d'autres failles — par exemple un jeton de sécurité dérivé d'un horodatage plutôt que d'une méthode cryptographique sûre.
Ne pas se fier au délai de réponse
Le délai de réponse ne prouve pas que le serveur a traité les requêtes en même temps : il peut les traiter simultanément mais répondre l'une après l'autre. Deux requêtes identiques auront un délai similaire, mais une requête modifiée génère une réponse différente, donc un délai différent.
Aide-mémoire¶
| Type | Approche |
|---|---|
| Dépassement de limite | Requêtes simultanées sur un endpoint à usage unique |
| Multi-étapes cachées | Abuser des sous-états (ex. contournement MFA) |
| Multi-endpoints | Réchauffement de connexion, abus de limite de débit |
| Endpoint unique | Requêtes parallèles à valeurs différentes (collision) |
| Construction partielle | Valeur correspondant à l'état non initialisé ([], nil) |
L'attaque à paquet unique (HTTP/2) via Turbo Intruder est l'outil de référence pour aligner les fenêtres de course ; un jeton de session distinct par requête permet de déjouer le verrouillage qui masque ces failles.