Smashing the State Machine : Démystifier une faille invisible

Table des matières

Introduction

Imaginez un coffre-fort, censé protéger vos secrets les plus précieux. Mais que se passerait-il si un ingénieux cambrioleur découvrait une faille dans son mécanisme, lui permettant de le manipuler à sa guise ? C’est l’essence même de la vulnérabilité « Smashing the State Machine », une menace informatique sournoise qui affecte des millions de machines à travers le monde.

Découverte par James Kettle, cette faille exploite une faiblesse insoupçonnée : les machines à états. Cachées au cœur de nos ordinateurs, smartphones et autres appareils connectés, ces machines invisibles orchestrent le fonctionnement de nombreux programmes. Mais comme tout système complexe, elles peuvent être la proie de pirates habiles, capables de les hacker.

L’impact de cette découverte est majeur. « Smashing the State Machine » affecte une large variété de logiciels, y compris des systèmes d’exploitation, des navigateurs web et des applications populaires. Des pirates exploitant cette vulnérabilité pourraient voler des données sensibles, prendre le contrôle de machines à distance ou même diffuser des logiciels malveillants.

Dans cet article, nous allons explorer ensemble les secrets de « Smashing the State Machine ». D’abord, nous démystifierons le concept d’état, élément crucial pour comprendre la vulnérabilité. Ensuite, nous plongerons dans les rouages de l’attaque, en découvrant comment les hackers peuvent exploiter cette vulnérabilité. Enfin, nous explorerons les conséquences concrètes de ces attaques et les solutions pour se protéger efficacement.

    Qu’est-ce qu’un « état » quand on parle de machine ?

    Un état, dans le contexte des machines à états, représente une situation particulière dans laquelle se trouve la machine. Il peut s’agir d’une étape précise du fonctionnement de la machine, d’une attente d’une saisie de l’utilisateur, d’une communication avec un autre appareil, etc.

    Prenons l’exemple d’un distributeur automatique de billets. Voici quelques exemples d’états que ce distributeur peut rencontrer :

    • État « Prêt à l’emploi » : Le distributeur est prêt à recevoir une carte bancaire et à effectuer une transaction.
    • État « Lecture de la carte » : Le distributeur lit les informations de la carte bancaire insérée par l’utilisateur.
    • État « Saisie du code PIN » : Le distributeur demande à l’utilisateur de saisir son code PIN.
    • État « Validation du code PIN » : Le distributeur vérifie la validité du code PIN saisi.
    • État « Sélection de l’opération » : L’utilisateur peut choisir l’opération qu’il souhaite effectuer (retrait d’argent, consultation du solde, etc.).
    • État « Distribution d’argent » : Le distributeur délivre l’argent demandé par l’utilisateur.
    • État « Fin de transaction » : La transaction est terminée et le distributeur est prêt à en commencer une nouvelle.

     

    Chaque état est associé à un ensemble d’actions que la machine peut effectuer. Par exemple, dans l’état « Lecture de la carte », le distributeur peut lire les informations de la carte bancaire et les stocker en mémoire. Dans l’état « Saisie du code PIN », le distributeur peut afficher un clavier numérique sur l’écran et attendre que l’utilisateur saisisse son code PIN.

    Les machines à états utilisent des transitions pour passer d’un état à l’autre. Une transition est un événement qui déclenche le passage de la machine à un nouvel état. Par exemple, dans l’exemple du distributeur automatique de billets, la transition entre l’état « Prêt à l’emploi » et l’état « Lecture de la carte » est déclenchée par l’insertion d’une carte bancaire dans le lecteur.

    Les transitions peuvent être conditionnelles. Cela signifie que la machine ne passe à l’état suivant que si une certaine condition est remplie. Par exemple, dans l’état « Validation du code PIN », la machine ne passera à l’état « Sélection de l’opération » que si le code PIN saisi est valide.

    Les machines à états peuvent être très complexes et avoir un grand nombre d’états et de transitions. Mais le principe de base reste le même : la machine se trouve dans un état donné à un moment donné et peut passer à un autre état en fonction des événements qui se produisent.

    En résumé, un « état » est une situation particulière dans laquelle se trouve une machine. Les machines à états utilisent des transitions pour passer d’un état à l’autre.

    L’exemple du distributeur automatique de billets nous a permis de comprendre le principe de base des machines à états. Mais comment cela se lie-t-il à l’attaque « Smashing the State Machine » ?

    Comprendre le principe multi-étapes est crucial. Que vous effectuiez une action sur un site web, une application ou tout autre système, il y aura toujours des étapes intermédiaires (visibles ou non) qui s’exécutent. Prenons l’exemple d’un serveur web :

    1. Vous saisissez votre nom d’utilisateur et votre mot de passe.
    2. Le serveur web authentifie vos informations.
    3. Si l’authentification est réussie, le serveur vous accorde l’accès aux données demandées.

     

    C’est là que l’attaque « Smashing the State Machine » intervient. Un pirate pourrait amener la machine dans un certain état afin d’obtenir un accès non autorisé a certaines données.

    Fonctionnement de l’attaque « Smashing the state machine »

    Avant de plonger dans les détails de l’attaque « Smashing the State Machine », il est important de comprendre un concept fondamental : les « race conditions », aussi appelées conditions de concurrence en français.

    Les « race conditions » : un bug invisible aux conséquences surprenantes

    Imaginez un site web qui vend des billets d’avion. ****Deux utilisateurs essaient d’acheter le dernier billet disponible en même temps. De façon logique, c’est le premier utilisateur qui accède au système et finalise l’achat qui obtient le billet. L’autre utilisateur se verra alors refuser la vente. Imaginez maintenant que les deux clients finalisent leur achat en même temps, le billet sera alors accordé aux deux, ce qui posera des problèmes au moment de l’embarquement.

    C’est ce qu’on appelle une « race condition ». Elles se produisent lorsque plusieurs processus s’exécutent simultanément et accèdent à la même ressource, comme une variable ou un fichier. Imaginez deux tâches distinctes qui traitent des requêtes sur un site web. Si ces tâches interagissent avec les mêmes données sans coordination, une « race condition » peut se produire.

    Dans cet exemple, l’accès au billet d’avion est la ressource critique. L’ordre dans lequel les utilisateurs accèdent à cette ressource détermine qui obtient le billet. Mais on aurait pu imaginer d’autres exemples tels que :

    • Utiliser une carte cadeau plusieurs fois
    • Utiliser un code de réduction plusieurs fois
    • Retirer des espèces plusieurs fois au delà du solde
    • Contourner une limite sur un site web (tel que l’anti-bruteforce)

     

    Les « race conditions » peuvent être dangereuses, le résultat final peut être imprévisible et causer divers problèmes :

    • Erreurs de calcul : Des résultats erronés peuvent être produits, comme un total incorrect dans une application de facturation.
    • Données corrompues : Des modifications inattendues peuvent affecter les données, comme la suppression accidentelle d’un fichier.
    • Dysfonctionnements du système : Des plantages, des gels ou des comportements inattendus peuvent se produire.

     

    Pour exploiter une “race conditions”, il est important de savoir combien de temps il faut pour exécuter les instructions et envoyer les requêtes, afin de profiter de la vulnérabilité pendant le court instant où elle est exploitable.

    La période de temps pendant laquelle une collision est possible est appelée « fenêtre de course » (”race window” en anglais). Cette fenêtre peut être très courte, comme une fraction de seconde, ou plus longue, selon le système et le contexte.

    L’attaque « Smashing the State Machine » exploite une « race condition » pour manipuler les transitions entre les états d’une machine.

    Il existe plusieurs techniques pour se protéger des « race conditions » :

    • Verrous : Verrouiller la ressource pendant qu’un processus l’utilise pour empêcher l’accès simultané d’autres processus.
    • Transactions atomiques : Grouper plusieurs opérations en une seule unité indivisible, garantissant qu’elles s’exécutent complètement ou pas du tout.

     

    Le “network jitter”

    Le « network jitter » (ou gigue en français) correspond à la variation du temps de latence entre l’émission et la réception d’un paquet de données sur un réseau. Il se traduit par une irrégularité du temps de transit, semblable à la variation du temps d’arrivée d’individus empruntant des itinéraires distincts pour se rendre à un point de rendez-vous.

    Le « network jitter » peut être causé par divers facteurs, dont :

    • Surcharge du réseau : Un trafic intense surchargeant les ressources réseau, comparable à une foule dense ralentissant la progression sur un chemin étroit.
    • Routage hétérogène : Les paquets empruntent des chemins différents avec des latences variables, à l’instar d’individus choisissant des itinéraires de longueur et de complexité différentes pour se rendre au même point.
    • Ressources limitées : La capacité insuffisante des routeurs ou des liens peut engendrer des retards dans la transmission des paquets, comme un goulot d’étranglement limitant le débit d’un fluide.
    • Interférences et erreurs : Des perturbations physiques ou des erreurs de transmission peuvent altérer la latence des paquets, tel que du bruit affectant la qualité d’un signal.

     

    Rappelez-vous que cette attaque vise à perturber le fonctionnement normal des systèmes en jouant sur le traitement des requêtes. Il faut donc que les différentes requêtes envoyées soient traitées dans le même état de la machine cible. Pour y parvenir, il est important d’analyser le temps écoulé entre l’exécution de la première requête et celle de la dernière. Plus cet intervalle est court, plus les chances sont élevées que toutes les requêtes soient traitées lorsque la machine se trouve dans le même état.

    Conséquences et exemple

    PortSwigger, une entreprise spécialisée dans la sécurité informatique, offre des ateliers permettant de s’exercer à diverses attaques web. Elle est célèbre pour avoir développé l’outil Burp Suite, largement utilisé.

    L’atelier proposé par Portswigger “https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun” nous permet de nous exercer à exploiter cette vulnérabilité. Voici le contexte :

    • Nous devons acheter un article d’une valeur de 1337 $.
    • Nous disposons uniquement de 50 $ pour effectuer cet achat.
    • Un coupon de réduction de 20 % est disponible, mais il est à usage unique.

     

    L’objectif est donc de parvenir à appliquer le coupon plusieurs fois afin de faire baisser le prix suffisamment pour qu’il reste dans la limite des 50 $ disponibles.

    Pour commencer, nous nous rendons dans le panier avec l’article sélectionné et nous appliquons le code promo une première fois.

    Après l’application du coupon, le prix de l’article est réduit à 1069 $, confirmant donc que le coupon a été utilisé avec succès.

    En utilisant l’outil Burp Suite pour effectuer une capture réseau. Une capture réseau avec Burp Suite consiste à enregistrer toutes les données qui sont échangées entre votre navigateur et les sites web que vous visitez. Cela peut inclure des requêtes que vous envoyez aux sites, les réponses que vous recevez en retour, ainsi que tout autre échange d’informations. Cette capture vous permet ensuite d’analyser ces données pour mieux comprendre comment fonctionne le site et repérer d’éventuelles vulnérabilités ou problèmes de sécurité.

    Nous avons pu récupérer la requête POST émise par le site au moment de l’application du code promo :

    Pour rappel une requête POST est utilisée pour envoyer des données à un serveur pour traitement, contrairement à la requête GET qui est utilisée pour demander des données à un serveur.

    En tentant de renvoyer la requête pour réappliquer le code, nous rencontrons un échec accompagné du message « Coupon already applied » (« coupon déjà appliqué » en français). À ce stade, nous avons identifié ce qui semble être notre point d’entrée potentiel dans le système (connu sous le terme « entry point » en anglais).

    De la même façon, si nous récupérons la requête permettant d’afficher le contenu de notre panier en utilisant une requête GET, nous constatons que notre article est bien affiché au prix de 1337 $ :

    En revanche, lorsque nous modifions le cookie de session, notre panier apparaît vide, ce qui indique que le site nous identifie bien via le cookie de session.

    Enfin, en rejouant la requête avec notre ancien cookie, nous retrouvons notre panier, ce qui suggère que celui-ci est lié à notre cookie et est stocké côté serveur.

    C’est ce dernier point qui nous permet d’identifier un risque potentiel de collision.

    Faisons une pause pour comprendre le schéma actuel supposé de la machine et les différents états qui la composent :

    L’état qui nous intéresse est donc “Application du code promo”. Nous allons tenter d’envoyer différentes requêtes dans cet état pour essayer de valider un code promo plusieurs fois. Il faut donc essayer de schématiser le traitement d’une requête par la machine dans cet état afin de prendre le compte le “network jitter”.

    L’objectif est donc d’envoyer plusieurs requêtes avant que la fin d’exécution de la première requête.

    Commençons par retirer le code promo du panier pour revenir à un panier ne contenant que l’article à 1337$. Autrement on ne pourrait pas appliquer de nouveau code promo.

    Avec la requête que nous avons précédemment obtenue et l’utilisation de l’outil BurpSuite, nous sommes prêts à lancer notre attaque. Pour ce faire, nous devons créer plusieurs requêtes similaires et les regrouper.

    Ensuite, nous lançons l’option « Send group (parallel) » afin de vérifier si nous parvenons à déclencher une collision en appliquant le code promo plusieurs fois.

    Nous constatons que cette méthode a réussi pour plusieurs requêtes mais a échoué pour d’autres. Il semble donc qu’il y ait une possibilité de collision et que nous avons réussi à l’exploiter.

    Dans ce cas, nous sommes passés d’un total de 1 337 $ avec le coupon initial à 547,63 $.

    C’est à ce moment que le « network jitter » devient crucial. Dans certains cas, nous aurions pu inverser des requêtes (si elles étaient différentes) ou ajouter des temps d’attente ou des requêtes inutiles pour ralentir une requête et nous laisser plus de temps pour exécuter davantage de requêtes en parallèle.

    Dans notre cas, nous avons simplement relancé la même opération, ce qui a suffi à atteindre notre objectif : faire en sorte que le prix de l’article soit inférieur ou égal à 50 $.

    En exploitant plusieurs fois un coupon à usage unique, nous avons pu acheter un article pour seulement 24,07 $ au lieu de son prix initial de 1337 $.

    Prévention et remédiation

    Pour expliquer les techniques de remédiations, il convient de revenir sur une notion clé en Base De Données : les transactions. Afin de répondre à différentes problématiques techniques et d’usage, la gestion de transactions a été créée.

    Mais finalement qu’est-ce qu’une transaction ? Une transaction est une suite d’opérations interrogeant et/ou modifiant la base de données.. Pour qu’une transaction soit considérée comme valide, toutes les opérations qu’elle contient doivent être valides elles-mêmes. On distingue deux types de transactions : les transactions réussies, où toutes les opérations sont correctement effectuées et la base de données se retrouve dans un nouvel état fonctionnel, et les transactions avortées, où aucune modification n’est apportée à la base de données.

    Pour les plus connaisseurs d’entre vous ou ceux qui travaillent régulièrement sur les bases de données, ces concepts sont souvent désignés par les termes « commit » pour les validations et « rollback commit » pour les annulations.

     

    Les transactions

    Mais comment est définie une transaction ?

    Une transaction repose sur plusieurs critères essentiels :

    • Atomicité : Une transaction est atomique, ce qui signifie qu’elle doit être soit entièrement valide et enregistrée dans la base de données, soit entièrement annulée en cas d’échec. Aucune partie de la transaction ne peut être enregistrée de manière partielle.
    • Cohérence : Avant et après une transaction, la base de données doit rester cohérente. Cela signifie que les données doivent respecter les contraintes d’intégrité définies, assurant ainsi la qualité et la validité des données.
    • Isolation : Chaque transaction doit être isolée des autres transactions concurrentes. Cela garantit que l’exécution d’une transaction n’interfère pas avec d’autres transactions en cours d’exécution, assurant ainsi la fiabilité du système.
    • Durabilité : Une fois qu’une transaction est confirmée, les modifications apportées à la base de données doivent être durables, même en cas de panne ou de crash du système. Cela garantit la continuité du service et la préservation des données même en cas de circonstances imprévues.

     

    Remédiation

    Pour remédier à la vulnérabilité présentée, plusieurs axes d’action sont envisageables :

    • Gestion rigoureuse des transactions : Mettre en place des mécanismes de gestion des transactions stricts et efficaces pour éviter les accès concurrents et garantir l’intégrité des données.
    • Vérification systématique de l’état des transactions : Assurer une vérification de la cohérence des résultats après chaque transaction afin de confirmer le respect de l’isolation entre les transactions. Cela implique une meilleure gestion du système de gestion de base de données pour éviter les conflits de stockage des données.
    • Système de détection et de défense : Déployer des outils de détection et de défense pour repérer et contrer toute tentative d’attaque, y compris les requêtes concurrentes ou autres actions malveillantes.
    • Gestion individuelle des sessions: Assurer une gestion individualisée des sessions utilisateur dans la base de données. Comme illustré dans cet article, l’attaque a été facilitée par le stockage des cookies de session utilisateur dans la base de données. Souvent, on cherche à optimiser les performances en permettant plusieurs requêtes de modification de sessions simultanées pour accueillir un plus grand nombre d’utilisateurs en même temps. Mais cette approche peut créer des vulnérabilités en permettant des conflits entre les requêtes concurrentes. Il est préférable de privilégier la sécurité plutôt que l’optimisation dans ce cas.

    Conclusion

    L’attaque “Smashing the State Machine” est une attaque extrêmement ancienne. Cependant, comme démontré dans l’article de portswigger, il est possible d’utiliser ce principe sur des infrastructures très répandues telles que les sites internet et, dans le cas que nous avons pu voir aujourd’hui, les sites de e-commerce.

    La réalisation d’une telle attaque ne nécessite finalement pas de connaissances techniques très avancées ni de matériel très coûteux ce qui en fait une attaque très dangereuse. En raison de sa rareté, cette attaque est peu connue tant par les auditeurs que par les développeurs, ce qui peut faciliter l’intégration d’une telle vulnérabilité au sein d’une application.