Un système de ticket - celui que vous voyez dans les festivals - fonctionne comme ceci: lorsqu'un utilisateur paie son ticket, une ligne est ajoutée à la base de données avec une colonne nommée is_scanned
, dont la valeur par défaut est définie sur false.
Dès qu'un gardien du festival scanne le code-barres (contenant un identifiant et un hachage unique) avec son appareil, une demande est envoyée à la base de données pour vérifier si:
is_scanned
est toujours défini sur false
.Si les deux conditions sont remplies, il définit la valeur is_scanned
à true
, pour empêcher quelqu'un d'autre de copier le ticket/code-barres d'entrer.
Le problème ici est le temps entre l'envoi de la demande par le périphérique de numérisation et la valeur is_scanned
basculé de false
à true
.
Considérez ce scénario: Alice a un ticket valide pour lequel elle a payé, mais ensuite elle laisse Eve copier son code-barres et change le nom visible sur le faux ticket d'Alice à Eve. Alors maintenant, nous avons deux billets. Un valide et un frauduleux, mais les deux ont le même code à barres, la seule différence est le nom.
Et si le ticket d'Alice et Eve était scanné exactement au même moment quand ils entrent dans le festival? Le système de ticket ne basculerait pas is_scanned
à true
à temps pour être sûr qu'Eve ne pourrait pas entrer avec le même code-barres qu'Alice. Il en résulte que les deux billets (valides et frauduleux) sont présentés comme "valides" aux gardes.
Bien sûr, ce genre d'exploit dépend vraiment de beaucoup de chance, et bien qu'il soit possible en théorie ... dans un scénario réel, cela échouerait probablement.
Cependant, comment pouvons-nous vaincre ce type d'exploit également en théorie?
J'ai déjà pris en compte cet exploit en utilisant la méthode suivante: Lorsqu'un code-barres est scanné, je affiche non seulement si le ticket est valide (remplit les conditions énoncées précédemment), mais également le nom dans la base de données. Si le nom ne correspond pas à celui du ticket, nous savons que le ticket est manipulé d'une manière ou d'une autre. De plus, si le nom qui apparaît sur le périphérique de numérisation ne correspond pas au nom sur l'ID (que tout le monde doit montrer de toute façon pour prouver l'âge), l'entrée est également interdite.
Le seul moyen de contourner cette solution est la fraude d'identité, et cela est bien entendu hors de la responsabilité du système de tickets à vérifier.
Une autre façon de résoudre ce problème, en théorie, consiste à ajouter un délai aléatoire entre chaque demande faite à la base de données/API de validation. De cette façon, personne ne pourrait scanner son ticket en même temps ... car le temps de validation est retardé à chaque fois avec un nombre aléatoire de millisecondes.
Je ne suis pas fan de ça, car ça:
is_scanned
de false
à true
, la seule solution serait de la retarder avec un intervalle de 50 ms minimum à chaque fois.Quelles autres solutions pensez-vous pour résoudre cet exploit?
La vulnérabilité que vous décrivez est une condition de concurrence .
Il existe plusieurs façons de le gérer, mais je choisirais un SELECT ... FOR UPDATE
Requête SQL, qui met un verrou sur les lignes sélectionnées pour empêcher de nouvelles écritures jusqu'à ce que la transaction en cours soit validée.
Assurez-vous de vérifier votre documentation RDBMS pour vérifier comment l'implémenter correctement:
L'autre solution ici est tout à fait juste et a du sens pour les grands systèmes où ce n'est pas aussi facile.
Avec les données dont vous disposez, c'est relativement simple, vous pouvez opter pour une option non bloquante:
UPDATE [FESTIVAL_TICKET]
SET IS_SCANNED = TRUE
WHERE TICKET_ID = @ScannedKey
AND IS_SCANNED = FALSE
Maintenant, c'est une opération atomique. Deux utilisateurs de la base de données ne peuvent pas émettre ceci et faire mettre à jour la ligne. La personne qui est renvoyée "1 ligne affectée" (il existe évidemment un moyen de le découvrir dans le code, ne pas analyser le texte pour cela) peut entrer. Tout le monde n'aura aucune ligne affectée par l'instruction. Si vous voulez être convivial, vous pouvez maintenant vérifier pourquoi il n'a pas pu être trouvé, s'il s'agissait d'un mauvais ID ou s'il est déjà numérisé.
Mais le détail important est que la déclaration est atomique. Un seul gagnera, peu importe à quel point ils sont proches de zéro décalage horaire. Parce que vous n'avez plus de lecture et puis une écriture. Vous avez la lecture et l'écriture en une seule opération atomique.
L'inconvénient de cela semble être que une personne pourrait y participer gratuit (avec un ticket copié).
Pour les petits événements, c'est probablement correct.
Cependant, si vous ajoutez trop de retard, pour une raison quelconque, vous risquez plus que une personne d'entrer.
Les scanners de billets laisseront passer quelques personnes supplémentaires si leurs appareils sont bloqués ou sont trop lents ... parce que, la plupart d'entre eux ont probablement des billets valides, non?
J'ai vu cela se produire lors d'un événement majeur de cette année civile auquel ont assisté des milliers de fans de musiciens qui beaucoup les gens ont entendu parler.
.
Mon groupe était l'un de ceux qui ont pu passer sans scan (et oui ... j'avais un ticket valide/légal).
Je devais rester là et regarder les preneurs de billets quelques minutes avant de comprendre pourquoi cela s'était produit.
TL; DR;
Ne codez pas pour chaque éventualité lorsque les gens sont impliqués.
Tirez sur deux ou trois neuf (99% -99,9%) et appelez-le un jour.
Enregistrez le nick picking pour quand seules les machines sont impliquées ... c'est là que vous pouvez obtenir un tas de 9.
Cette réponse a déjà une réponse intéressante et bon marché, cependant, j'ajouterais ma propre réponse à la fois d'un point de vue de génie logiciel et de sécurité. Cela peut être utile pour de futures questions similaires sur les exploits improbables .
et même si c'est possible en théorie ... Dans un scénario réel, cela échouerait probablement.
Et puis, quel est le dommage potentiel par rapport au coût? Je vais prouver que l'effort de dépenses et les maux de tête sur une sécurité supplémentaire ne valent pas le risque.
Maintenant, la solution déjà proposée et acceptée pour gérer correctement les conditions de concurrence avec les transactions SQL, transférant la responsabilité/le coût à la base de données, est la meilleure solution, standard et moins chère. Cela pourrait être la fin du cas, car la réponse a en effet été acceptée .
Comme nous l'avons déjà souligné, l'événement où les deux participants sont scannés au moment très précis et déclenchent l'exploit de la condition de concurrence pourrait être estimé en cotes de millions sinon milliards de magnitude. Pour vous donner une idée qualitative des cotes du milliardaire, lisez cet article sur les loteries et découvrez que jouer pour ce SuperEnalotto en plus de la deuxième liste pourrait être un jeu facile par rapport au scan de deux tickets, et la récompense est définitivement cohérente. Les cotes représentent l'exploitabilité d'une vulnérabilité et qui est normalement qualifiée en niveaux discrets ([très] peu probable, [très] probable). Je compare toujours les événements liés à la sécurité non déterministes à la loterie pour fournir une comparaison plus familière.
Pour apporter des précisions supplémentaires, les chances sont influencées par:
Voici donc la considération d'ingénierie logicielle.
Le billet a un prix, vaut donc X $. J'estime que la magnitude de X peut être de l'ordre de 50 à 100. Pour chaque personne exploitant la vulnérabilité et pénétrant frauduleusement dans l'installation, une perte de X $ s'applique.
La mise en œuvre de contrôles plus complexes (par exemple, le contrôle du nom du passeport) coûte cher à la fois en termes de code requis dans la phase de développement du logiciel et de temps nécessaire les personnes à entrer dans l'installation. Les agents de sécurité sont payés à l'heure. Mettre en œuvre un contrôle de sécurité de type Ben-Gurion * est beaucoup plus cher et douloureux pour les honnêtes.
Maintenant, vous voulez mieux dormir, assuré que personne ne peut exploiter votre système. Combien ça coûte? Après avoir payé une somme exorbitante pour sécuriser votre système, vous découvrirez peut-être que votre concurrent, qui exécute un système "non protégé", tolère une perte de 80 $ avec une chance de plus d'un million de magnitude. Il est difficile de quantifier cette probabilité. Puisque vous avez plus de chances de gagner à la loterie la plus difficile du monde, vous feriez mieux de parier pour quitter votre emploi pour de bon!
Conclusion: dans notre métier, les cotes gagnantes sont notre meilleur partenaire pour dormir!
Conclusion 2: les attaques de conditions de concurrence peuvent être probablement exploitées sur des systèmes de réseau automatisés où les attaquants peuvent évidemment se synchroniser à la microseconde !!! Cela pourrait également multiplier les dégâts, alors accueillez donc les meilleures mesures de sécurité!
Conclusion 3: si le système est déjà en marche, l'effort de le patcher avec la réponse acceptée (conception, développement, test, UAT, déploiement, PMO ...) est plus cher que les dommages potentiels. Veuillez commenter ci-dessous
* J'ai cité cela comme exemple parce que les lignes de sécurité des aéroports en Israël sont légendairement longues et complètes
Il y a déjà de bonnes réponses ici qui couvraient une grande partie des exploits des parties de la base de données. Mais je voulais ajouter mon expérience de la vie réelle , après avoir travaillé dans le domaine de l'événement (festival en plein air) et conçu le système et les applications de validation des billets.
L'un des grands défis est la stabilité du réseau, car l'hypothèse selon laquelle tous les périphériques de numérisation ont une capacité réseau tout le temps est tout à fait erronée. Il peut y avoir des retards, des interruptions ou une indisponibilité à tout moment pendant le processus de numérisation et cela ne devrait pas retarder les entrées des clients à l'événement (du moins de notre point de vue, d'autres événements peuvent nécessiter une validation plus stricte).
Dans notre application, les tickets ont été validés à l'aide d'une signature, mais ils ont été synchronisés et validés dans la base de données uniquement lorsque le réseau était en place. L'application a stocké les tickets validés/à valider dans un compartiment et a essayé de valider autant de tickets que possible une fois le réseau disponible. Cela évite également de faire un INSERT
par ticket.
Le plus éloigné de l'entrée de l'événement, le wifi n'a pas atteint du tout. Pour économiser le coût d'avoir un autre routeur pour seulement 10 mètres de couverture supplémentaire, les périphériques de numérisation pourraient communiquer entre eux et partager leurs connexions. Cela signifie que si un seul appareil avait accès au wifi, les autres pouvaient théoriquement lui envoyer leurs seaux, ou à l'appareil le plus proche à portée qui le transmettrait.
La vraie vie nous a montré que la plupart des appareils de numérisation ont perdu la connexion au moins une fois par minute.
Donc théoriquement, un ticket pourrait être scanné par autant d'appareils non connectés qu'il le souhaite, mais seulement une fois par appareil. C'est une condition de concurrence , mais beaucoup plus triviale pour exploiter que ce que les autres réponses ont mentionné.
Un mot sur le How to prevent it?
:
Vous pouvez empêcher la condition de concurrence, en supprimant le parallélisme, ce que fait un verrou. Il introduira des latences car vous réduisez essentiellement la capacité de votre base de données à accepter des écritures simultanées.
La question devient alors: vaut-elle la peine? Pouvons-nous accepter plus de retard jusqu'à la validation pour garantir une authentification correcte? Cela empêchera-t-il les gens de trouver une porte ouverte ou de sauter une clôture?
Une bonne solution pourrait être d'utiliser une file d'attente de messages au lieu de mettre des retards. Le ticket scanné n'est pas traité immédiatement, il attend que tous les tickets envoyés soient traités. Et le système ne renvoie pas de réponse à moins que le ticket n'ait quitté la file d'attente et n'ait été traité correctement. Un argument serait que cela pourrait être lent car tout le monde doit attendre que les autres finissent. mais vous pouvez répartir logiquement les ID de ticket, par exemple: 2 files d'attente, 1 pour les tickets impairs et 1 pour les tickets pairs. ou tout simplement mettre les numéros de groupe dans l'id lui-même.