TL; DR Cet article décrit des astuces impopulaires sur les conditions de compétition qui ne sont pas couramment utilisées dans ce type d'attaque. Sur la base des résultats de nos recherches, nous avons créé notre propre framework pour les attaques racepwn.
Vasya veut transférer 100 dollars qu'il a sur son compte, Petya. Il va dans l'onglet des transferts, conduit le surnom de Petin dans le champ avec le montant des fonds à transférer - le nombre 100. Ensuite, clique sur le bouton de transfert. Les données à qui et combien sont envoyées à l'application Web. Que peut-il se passer à l'intérieur? Que doit faire le programmeur pour que tout fonctionne correctement?
- Vous devez vous assurer que le montant est disponible pour Vasya pour le transfert.
Il est nĂ©cessaire d'obtenir la valeur du solde actuel de l'utilisateur, s'il est infĂ©rieur au montant qu'il souhaite transfĂ©rer, lui en parler. Compte tenu du fait que notre site n'accorde pas de prĂȘts et ne devrait pas avoir un solde nĂ©gatif.
- Soustraire le montant à transférer du solde de l'utilisateur
Il est nécessaire de noter le solde de l'utilisateur actuel, moins le montant transféré. C'était 100, c'est devenu 100-100 = 0.
- Ajoutez au solde de l'utilisateur Petya le montant qui a été transféré.
Petya, au contraire, était 0, il est devenu 0 + 100 = 100.
- Affichez un message Ă l'utilisateur qu'il est bien fait!
Lors de l'écriture de programmes, une personne prend les algorithmes les plus simples, qu'elle combine en un seul tracé, qui sera le script du programme. Dans notre cas, la tùche du programmeur est d'écrire la logique des transferts d'argent (points, crédits) d'une personne à une autre dans une application web. Guidé par la logique, vous pouvez créer un algorithme composé de plusieurs vérifications. Imaginez que nous venons de supprimer tout ce qui est inutile et compilé un pseudocode.
(. >= _) .=.-_ .=.+_ () ()
Mais tout irait bien si tout se passait Ă son tour. Mais un site peut servir de nombreux utilisateurs en mĂȘme temps, et cela ne se produit pas dans un seul thread, car les applications Web modernes utilisent le multitraitement et le multithreading pour le traitement parallĂšle des donnĂ©es. Avec l'avĂšnement du multithreading, les programmes ont une vulnĂ©rabilitĂ© architecturale amusante - condition de concurrence (ou condition de concurrence).
Imaginez maintenant que notre algorithme fonctionne simultanément 3 fois.
Vasya a encore 100 points sur son solde, mais d'une maniĂšre ou d'une autre, il s'est tournĂ© vers l'application Web en trois threads en mĂȘme temps (avec un minimum de temps entre les demandes). Les trois flux vĂ©rifient si l'utilisateur est Petya et vĂ©rifient si Vasya a suffisamment d'Ă©quilibre pour le transfert. Au moment oĂč l'algorithme vĂ©rifie le solde, il est toujours Ă©gal Ă 100. Une fois la vĂ©rification terminĂ©e, 100 est soustrait du solde actuel 3 fois et Pete est ajoutĂ©.
Qu'avons-nous? Vasya a un solde nĂ©gatif sur son compte (100 - 300 = -200 points). Dans l'intervalle, Petya a 300 points, bien qu'en fait il devrait ĂȘtre de 100. C'est un exemple typique de l'exploitation d'une condition de course. C'est comparable au fait que plusieurs personnes passent sur un seul passage Ă la fois. Voici une capture d'Ă©cran de cette situation de
4lemon
La condition de concurrence peut ĂȘtre dans des applications multithreads, ainsi que dans les bases de donnĂ©es dans lesquelles elles fonctionnent. Pas nĂ©cessairement dans les applications Web, par exemple, il s'agit d'un critĂšre commun pour l'escalade de privilĂšges dans les systĂšmes d'exploitation. Bien que les applications Web aient leurs propres caractĂ©ristiques pour un fonctionnement rĂ©ussi, je veux en parler.
Fonctionnement typique des conditions de course
Un hacker entre dans une salle de narguilĂ©, une quĂȘte et un bar, et pour lui - vous avez une condition de course! Omar Ganiev
Dans la plupart des cas, un logiciel multithread est utilisĂ© en tant que client pour vĂ©rifier / faire fonctionner la condition de concurrence. Par exemple, Burp Suite et son outil Intruder. Ils ont mis une requĂȘte HTTP pour la rĂ©pĂ©tition, installĂ© de nombreux flux et allumĂ© le dĂ©luge. Comme par exemple
dans cet article . Ou
dans celui-ci . Il s'agit d'une mĂ©thode assez efficace, si le serveur autorise l'utilisation de plusieurs threads pour sa ressource, et comme ils le disent dans les articles ci-dessus, si cela ne fonctionne pas, essayez Ă nouveau. Mais le fait est que dans certaines situations, cela peut ne pas ĂȘtre efficace. Surtout si vous vous souvenez comment ces applications accĂšdent au serveur.
Qu'y a-t-il sur le serveur
Chaque thread Ă©tablit une connexion TCP, envoie des donnĂ©es, attend une rĂ©ponse, ferme la connexion, s'ouvre Ă nouveau, envoie des donnĂ©es, etc. Ă premiĂšre vue, toutes les donnĂ©es sont envoyĂ©es simultanĂ©ment, mais les requĂȘtes HTTP elles-mĂȘmes peuvent ne pas arriver de maniĂšre synchrone et sont incohĂ©rentes en raison de la nature de la couche de transport, de la nĂ©cessitĂ© d'Ă©tablir une connexion sĂ©curisĂ©e (HTTPS) et de rĂ©soudre le DNS (pas dans le cas de burp) et de nombreuses couches abstractions qui transmettent des donnĂ©es avant d'ĂȘtre envoyĂ©es Ă un pĂ©riphĂ©rique rĂ©seau. En matiĂšre de millisecondes, cela peut jouer un rĂŽle clĂ©.
Pipelining HTTP
Vous pouvez rappeler HTTP-Pipelining, dans lequel vous pouvez envoyer des donnĂ©es Ă l'aide d'un seul socket. Vous pouvez voir par vous-mĂȘme comment cela fonctionne en utilisant l'utilitaire netcat (vous avez GNU / Linux, non?).
En fait, vous devez utiliser Linux pour de nombreuses raisons, car il existe une pile TCP / IP plus moderne, qui est prise en charge par les noyaux du systÚme d'exploitation. Le serveur est probablement aussi dessus.Par exemple, exécutez
nc google.com 80 et insérez-y les lignes
GET / HTTP/1.1 Host: google.com GET / HTTP/1.1 Host: google.com GET / HTTP/1.1 Host: google.com
Ainsi, au sein d'une mĂȘme connexion, trois requĂȘtes HTTP seront envoyĂ©es et vous recevrez trois rĂ©ponses HTTP. Cette fonctionnalitĂ© peut ĂȘtre utilisĂ©e pour rĂ©duire le temps entre les demandes.
Qu'y a-t-il sur le serveur
Le serveur Web recevra les demandes sĂ©quentiellement (mot-clĂ©) et traitera les rĂ©ponses par ordre de prioritĂ©. Cette fonctionnalitĂ© peut ĂȘtre utilisĂ©e pour attaquer en plusieurs Ă©tapes (quand il est nĂ©cessaire d'effectuer sĂ©quentiellement deux actions dans le minimum de temps) ou, par exemple, pour ralentir le serveur dans la premiĂšre requĂȘte afin d'augmenter le succĂšs de l'attaque.
Astuce - vous pouvez empĂȘcher le serveur de traiter votre demande en chargeant son SGBD, surtout si INSERT / UPDATE est utilisĂ©. Des demandes plus lourdes peuvent «ralentir» votre charge, il est donc plus probable que vous gagniez cette course.
Fractionner une requĂȘte HTTP en deux parties
Tout d'abord, rappelez-vous comment la requĂȘte HTTP est gĂ©nĂ©rĂ©e. Eh bien, comme vous le savez, la premiĂšre ligne est la version de la mĂ©thode, du chemin et du protocole:
GET / HTTP/1.1
Voici les en-tĂȘtes avant le saut de ligne:
Host: google.com
Cookie: a=1
Mais comment le serveur Web sait-il que la requĂȘte HTTP est terminĂ©e?
Regardons un exemple, entrez
nc google.com 80 , et lĂ
GET / HTTP/1.1
Host: google.com
GET / HTTP/1.1
Host: google.com
, aprĂšs avoir appuyĂ© sur ENTRĂE, rien ne se passera. Cliquez Ă nouveau - vous verrez la rĂ©ponse.
Autrement dit, pour que le serveur Web accepte la demande HTTP, deux sauts de ligne sont nĂ©cessaires. Une requĂȘte valide ressemble Ă ceci:
GET / HTTP/1.1\r\nHost: google.com\r\n\r\n
S'il s'agissait de la mĂ©thode POST (n'oubliez pas Content-Length), la requĂȘte HTTP correcte serait la suivante:
POST / HTTP/1.1
Host: google.com
Content-Length: 3
a=1
ou
POST / HTTP/1.1\r\nHost: google.com\r\nContent-Length: 3\r\n\r\na=1
Essayez d'envoyer une demande similaire Ă partir de la ligne de commande:
echo -ne "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n" | nc google.com 80
En conséquence, vous recevrez une réponse, car notre demande HTTP est terminée. Mais si vous supprimez le dernier \ n caractÚre, vous n'obtiendrez pas de réponse.
En fait, de nombreux serveurs Web doivent simplement utiliser \ n comme transfert, il est donc important de ne pas échanger \ r et \ n, sinon d'autres astuces risquent de ne pas fonctionner.
Qu'est-ce que ça donne? Vous pouvez ouvrir simultanĂ©ment de nombreuses connexions Ă une ressource, envoyer 99% de votre requĂȘte HTTP et laisser le dernier octet non envoyĂ©. Le serveur attendra que vous atteigniez le dernier saut de ligne. Une fois qu'il sera clair que la partie principale des donnĂ©es a Ă©tĂ© envoyĂ©e, envoyez le dernier octet (ou plusieurs).
Ceci est particuliĂšrement important lorsqu'il s'agit d'une demande POST volumineuse, par exemple, lorsque le tĂ©lĂ©chargement de fichiers est nĂ©cessaire. Mais mĂȘme dans une petite demande, cela a du sens, car la livraison de quelques octets est beaucoup plus rapide que simultanĂ©ment des kilo-octets d'informations.
Délai avant l'envoi de la deuxiÚme partie de la demande
Selon les recherches de
Vlad Roskov , il est non seulement nĂ©cessaire de diviser la demande, mais il est Ă©galement judicieux de faire un dĂ©lai de plusieurs secondes entre l'envoi de la partie principale des donnĂ©es et la derniĂšre. Et tout cela parce que les serveurs Web commencent Ă analyser les demandes avant mĂȘme de les recevoir dans leur intĂ©gralitĂ©.

Qu'y a-t-il sur le serveur
Par exemple, lors de la rĂ©ception des en-tĂȘtes de requĂȘte HTTP, nginx commencera Ă les analyser, mettant en cache la requĂȘte dĂ©fectueuse. Lorsque le dernier octet arrive, le serveur Web prend la demande partiellement traitĂ©e et l'envoie directement Ă l'application, rĂ©duisant ainsi le temps de traitement des demandes, ce qui augmente la probabilitĂ© d'une attaque.
Comment y faire face
Tout d'abord, c'est bien sûr un problÚme architectural, si vous concevez correctement une application web, vous pouvez éviter de telles courses.
En rÚgle générale, les méthodes de contrÎle d'attaque suivantes sont utilisées:
L'opération bloque l'accÚs à l'objet verrouillé dans le SGBD jusqu'à ce qu'il soit débloqué. D'autres se tiennent debout et attendent sur la touche. Il est nécessaire de travailler correctement avec les serrures, pour ne rien bloquer de superflu.
Transactions ordonnées (sérialisables) - assurez-vous que les transactions seront effectuées de maniÚre strictement séquentielle, mais cela peut affecter les performances.
Prenez quelque chose (par exemple etcd). Au moment de l'appel des fonctions, une entrée avec une clé est créée, s'il n'était pas possible de créer une entrée, alors elle existe déjà et la demande est interrompue. à la fin du traitement de la demande, l'enregistrement est supprimé.
Et en général, j'ai aimé la
vidéo d'Ivan the Hard Worker sur les verrous et les transactions, trÚs informative.
Fonctions de session en condition de course
L'une des caractĂ©ristiques des sĂ©ances peut ĂȘtre qu'elle interfĂšre en soi avec l'exploitation de la course. Par exemple, en PHP, aprĂšs session_start (), un fichier de session est verrouillĂ© et son dĂ©verrouillage ne se produit qu'Ă la fin du script (s'il n'y a pas eu d'appel Ă
session_write_close ). Si un autre script qui utilise une session est appelé à ce moment, il attendra.
Pour contourner cette fonctionnalitĂ©, vous pouvez utiliser une astuce simple - pour vous authentifier autant de fois que nĂ©cessaire. Si l'application Web vous permet de crĂ©er plusieurs sessions pour un seul utilisateur, il vous suffit de collecter tous les PHPSESSID et de faire de chaque requĂȘte son propre identifiant.
Proximité du serveur
Si le site sur lequel vous souhaitez opérer la condition de concurrence est hébergé dans AWS - prenez la voiture dans AWS. Si dans DigitalOcean - apportez-le là .
Lorsque la tĂąche est d'envoyer des requĂȘtes et de minimiser l'intervalle d'envoi entre elles, la proximitĂ© immĂ©diate avec le serveur web sera sans aucun doute un plus.
AprĂšs tout, il y a une diffĂ©rence lors du ping vers le serveur 200 et 10 ms. Et si vous avez de la chance, vous pouvez mĂȘme vous retrouver sur le mĂȘme serveur physique, alors ce sera un peu plus facile de voler :)
Pour résumer
Pour une condition de course réussie, vous pouvez appliquer diverses astuces pour augmenter les chances de succÚs. Envoyez plusieurs demandes persistantes en une seule, ce qui ralentit le serveur Web. Divisez la demande en plusieurs parties et créez un délai avant l'envoi. Réduisez la distance au serveur et le nombre d'abstractions à l'interface réseau.
à la suite de cette analyse, avec Michail Badin, nous avons développé l'outil
RacePWNIl se compose de deux éléments:
- BibliothĂšque C librace, qui envoie un grand nombre de requĂȘtes HTTP au serveur dans les plus brefs dĂ©lais et en utilisant la plupart des fonctionnalitĂ©s de l'article
- Utilitaires racepwn, qui accepte la configuration json et dirige généralement cette bibliothÚque
RacePWN peut ĂȘtre intĂ©grĂ© Ă d'autres utilitaires (par exemple, dans Burp Suite), ou vous pouvez crĂ©er une interface Web pour gĂ©rer les vols (vous ne pouvez toujours pas mettre la main dessus). Profitez-en!
Mais en réalité, il y a encore de la place pour se développer et vous pouvez vous rappeler HTTP / 2 et ses perspectives d'attaque. Mais pour le moment, HTTP / 2, la plupart des ressources n'ont qu'une demande de mandataire avant vers le bon vieux HTTP / 1.1.
Peut-ĂȘtre connaissez-vous d'autres subtilitĂ©s?
L'original