L'appel du vendredi soir
Un ami, qui dirige un hôtel indépendant, me contacte. Le ton est inquiet. Sa réceptionniste vient de lui signaler qu'en épluchant les réservations de la semaine, elle a trouvé une transaction qui cloche. Un client a réservé et occupé une chambre standard deux nuits, mais le montant débité sur la facture correspond à environ 20% du tarif public. Le client est reparti, la chambre a été nettoyée, tout s'est déroulé normalement côté opérationnel. Sauf le prix.
La première hypothèse qu'il formule est celle du piratage. Accès frauduleux au PMS, compromission du compte d'un employé, carte bancaire volée de quelqu'un du staff qui aurait bricolé une réservation de faveur. Il s'attend à devoir porter plainte et à engager des démarches lourdes.
Je lui demande de me laisser regarder avant de déclencher quoi que ce soit. L'expérience a appris à notre métier que les anomalies de prix sur un site de réservation ne viennent presque jamais d'un piratage au sens où l'entend le grand public. Elles viennent, neuf fois sur dix, de la logique applicative du site lui-même.
Ce que j'ai trouvé en trente minutes
Avec son accord écrit, je lance une session de test sur son moteur de réservation. Objectif : comprendre comment un client a pu finaliser une réservation à un prix aussi bas. Je prends la même chambre, aux mêmes dates que le cas suspect, je lance le parcours de réservation en interceptant le trafic HTTP avec un outil standard de pentest.
Tout est en place côté interface : les prix sont affichés correctement, le récapitulatif panier est propre, le formulaire de code promo accepte bien les codes partenaires et les rejette s'ils sont inconnus. Un code promo saisonnier est en cours, avec une réduction prévue pour les clients fidèles. Ce code est annoncé comme « utilisable une seule fois par client ».
En examinant les requêtes HTTP échangées entre le navigateur et le serveur, je comprends rapidement le problème. Le site vérifie correctement qu'un coupon n'a pas déjà été utilisé, mais cette vérification et l'enregistrement de l'utilisation ne sont pas encapsulés dans une opération atomique. Conséquence : si plusieurs requêtes d'application du coupon arrivent au serveur dans la même milliseconde, elles passent toutes le contrôle avant qu'aucune n'ait eu le temps de marquer le coupon comme consommé. La réduction est appliquée autant de fois qu'il y a de requêtes.
Le client qui a payé 20% du prix n'a pas piraté quoi que ce soit au sens classique. Il a utilisé un outil public (Burp Suite, en version gratuite) pour envoyer sa requête d'application du code promo cinq fois en parallèle. Le site a appliqué cinq réductions au lieu d'une. La réduction initiale de 20% s'est transformée en une remise cumulée de 80%, et le client a payé le reliquat avec sa vraie carte bancaire, sans aucune fraude au sens légal du paiement. Aux yeux de la banque et du serveur, la transaction est parfaitement légitime.
Après divulgation responsable et correctif appliqué par son prestataire technique, mon ami m'a autorisé à publier ce retour d'expérience. Les détails propres à son hôtel sont volontairement absents de cet article.
Comment ça fonctionne techniquement : la race condition
Pour les équipes techniques, développeurs et DSI qui veulent comprendre le mécanisme exact, entrons dans le détail. La faille rencontrée ici appartient à la famille des race conditions, plus précisément au sous-type limit overrun. Elle est référencée CWE-362 et CWE-367 (Time-of-check Time-of-use) dans la nomenclature MITRE.
Le principe est le suivant. Lorsqu'une application reçoit une requête d'application de code promo, elle exécute typiquement cette séquence :
- Lire en base l'état du coupon pour ce client
- Vérifier qu'il n'a pas déjà été utilisé
- Calculer et appliquer la réduction au panier
- Marquer le coupon comme utilisé en base
Entre la lecture de l'état (étape 1) et l'écriture du marquage (étape 4), il s'écoule quelques millisecondes. C'est la fenêtre TOCTOU, pour Time-Of-Check to Time-Of-Use. Si plusieurs requêtes identiques arrivent dans cette fenêtre, elles lisent toutes la même valeur « non utilisé », elles appliquent toutes la réduction, et elles écrivent toutes leur marquage, mais la réduction a déjà été appliquée plusieurs fois au panier avant que le premier marquage ait été pris en compte.
Démonstration sur un environnement pédagogique
Pour illustrer concrètement, je ne vais évidemment pas rejouer l'attaque sur le site de mon ami. Il existe pour cela des environnements d'entraînement publics et légaux, mis à disposition par les éditeurs d'outils de sécurité. Le plus connu est la Web Security Academy de PortSwigger, qui propose un laboratoire nommé « Limit overrun race conditions ». C'est ce laboratoire que j'utilise pour la démonstration qui suit.
Le scénario du lab reproduit fidèlement ce qu'on rencontre en production : une boutique en ligne, un article cher, un code promo PROMO20 donnant 20% de réduction et annoncé comme à usage unique.
Étape 1 Capture de la requête d'application du coupon
Après avoir ajouté l'article au panier, je soumets le code promo via le formulaire prévu. La requête HTTP qui part vers le serveur est capturée par Burp Proxy. Elle ressemble à ceci :
POST /cart/coupon HTTP/2
Host: target.lab
Cookie: session=xxx
Content-Type: application/x-www-form-urlencoded
csrf=AbCdEf123&coupon=PROMO20
PROMO20 appliqué une fois réduit bien le panier de 20%.Étape 2 Mise en évidence du contrôle côté interface
Si j'essaie de soumettre à nouveau le même code via l'interface, le site affiche un message d'erreur du type « Coupon already applied ». Le contrôle existe donc bien, et la protection fonctionne dans un usage normal, séquentiel, du site. C'est précisément ce qui donne une fausse impression de sécurité.
Étape 3 Préparation de l'envoi parallèle
La requête capturée est envoyée vers l'outil Repeater de Burp. Là, je crée un groupe d'onglets et je duplique la requête vingt fois. Chaque onglet contient exactement la même requête, avec les mêmes cookies, le même jeton CSRF, le même code promo.
Étape 4 Déclenchement du single-packet attack
Le menu déroulant du bouton Send propose une option nommée « Send group in parallel (single-packet attack) ». Cette fonctionnalité s'appuie sur le multiplexage HTTP/2 pour embarquer les vingt requêtes dans un seul paquet TCP, qui arrive donc sur le serveur avec une synchronisation à la microseconde. C'est ce qui rend l'exploitation fiable là où elle était historiquement très aléatoire.
Cette technique a été formalisée et popularisée par James Kettle, directeur de recherche chez PortSwigger, en 2023. Elle a changé la donne sur cette classe de vulnérabilités : ce qui était considéré comme difficilement exploitable en conditions réelles est devenu une routine de test en quelques clics.
Étape 5 Observation du résultat
Les vingt réponses du serveur reviennent dans les vingt onglets. En rafraîchissant la page panier, on constate que le coupon a été appliqué plusieurs fois, typiquement entre quatre et dix fois selon le timing et la configuration du serveur. Le prix de l'article s'effondre. Dans le cas du lab, il descend assez bas pour passer sous le budget fixé par l'énoncé, et la commande peut être finalisée.
Dans le cas de l'hôtel de mon ami, le client a probablement observé le même phénomène : cinq à six applications du coupon, une réduction cumulée bien supérieure à celle prévue, et une facture réglée sans accroc par sa propre carte bancaire.
Pourquoi cette faille est particulièrement piégeuse pour l'hôtellerie
Trois caractéristiques rendent les race conditions redoutables dans le secteur de l'hôtellerie en ligne :
- L'invisibilité côté gestion. La réservation apparaît dans le PMS comme une réservation normale, avec paiement validé. Aucune alerte, aucune notification. La découverte se fait souvent plusieurs jours ou plusieurs semaines après, par recoupement comptable quand elle se fait.
- L'absence d'intention frauduleuse détectable. Le client paie le reliquat avec sa vraie carte bancaire. Pas de fraude au paiement, pas de chargeback à redouter, pas de signal dans les outils anti-fraude bancaires classiques. C'est le site lui-même qui a calculé le mauvais prix.
- La reproductibilité. Une fois la faille identifiée et partagée sur un forum spécialisé, elle peut être exploitée par des dizaines de personnes sur la même courte période, avant même que l'établissement ne détecte la première anomalie.
Et surtout, cette classe de vulnérabilité n'est qu'un exemple parmi d'autres. Les moteurs de réservation, en particulier ceux développés sur mesure il y a plusieurs années ou composés de briques tierces mal intégrées, cumulent régulièrement plusieurs failles de logique métier : manipulation du prix depuis le navigateur, confusion d'identifiants de réservation permettant de consulter ou modifier le dossier d'un autre client, contournement d'étapes du workflow de paiement, défauts d'authentification sur les portails professionnels. Aucune de ces failles ne se détecte avec un scanner automatique standard. Elles sortent d'un audit manuel conduit par un pentester qui prend le temps de comprendre la logique métier spécifique de votre plateforme.
Les contre-mesures à mettre en place
Pour les équipes techniques, la protection contre cette classe de vulnérabilité repose sur plusieurs mécanismes complémentaires :
- Verrous en base de données. Utiliser des verrous explicites de type
SELECT ... FOR UPDATEpour sérialiser l'accès à la ligne du coupon pendant la durée de la transaction. - Transactions atomiques avec isolation stricte. Passer les opérations critiques en niveau d'isolation
SERIALIZABLE, qui détecte et rejette les conflits entre transactions concurrentes. - Contrainte d'unicité en base. Poser une contrainte
UNIQUEsur la combinaison coupon/commande ou coupon/client. Même si dix threads tentent d'insérer simultanément, la base n'en acceptera qu'une seule, et les autres remonteront une erreur gérable côté application. - Clés d'idempotence. Imposer qu'une requête sensible transporte un identifiant unique généré côté client, et rejeter les doublons côté serveur.
- Limitation de débit. Plafonner le nombre de requêtes par session et par adresse IP sur les endpoints critiques. À considérer comme une mesure de défense en profondeur, pas comme la solution principale.
Pentest hôtellerie sur la Côte d'Azur : un enjeu local
La question du pentest hôtel est particulièrement sensible sur la Côte d'Azur, où la densité d'établissements indépendants, de palaces et de résidences haut de gamme est parmi les plus élevées d'Europe. Entre Cannes, Nice, Monaco, Antibes, Sophia Antipolis, Mandelieu, Saint-Raphaël et Menton, plusieurs milliers d'hôtels et de résidences de tourisme exploitent un moteur de réservation en ligne, souvent couplé à un PMS et à des portails partenaires (tour-opérateurs, OTA, apporteurs d'affaires B2B).
Un test d'intrusion hôtelier efficace ne se limite pas au scan automatique de la homepage. Il couvre la logique métier du moteur de réservation (race conditions, manipulation de prix, confusion d'identifiants entre dossiers clients, contournement d'étapes du workflow de paiement), l'exposition du PMS, l'authentification du back-office, les portails partenaires, et le traitement des données clients soumis au RGPD et, pour les plus gros établissements, à la directive NIS2. C'est ce type d'audit de sécurité hôtel que HackHeart mène sur site chez ses clients de la région PACA, en mode boîte grise, avec cadrage précis et rapport actionnable à destination du prestataire technique de l'hôtel.
Article rédigé après divulgation responsable et correction de la faille. Les détails relatifs à l'établissement mentionné en introduction ont été anonymisés.