Pour des raisons de confidentialité, et aussi parce que des attaquants ou des scrapers peuvent lire ces lignes, une partie des données a été modifiée pour anonymiser le cas : le nom de domaine, les pages ciblées et les identifiants des règles en place ne sont pas reproduits. Les volumes, les signatures et le raisonnement sont eux fidèles aux mesures, la vague étant toujours active au moment de la rédaction. Les captures d'écran du dashboard ont été recadrées dans le même but.

Le site et le symptôme

Une place de marché européenne met en relation des professionnels. Elle publie à cette fin un catalogue public de plusieurs centaines de milliers de pages d'offres, dont les prix sont observés à la fois par les concurrents et par les clients de la plateforme. Le catalogue est interrogé par le site et par une application mobile qui partage le même domaine, pour environ cinq millions de requêtes par jour.

L'administrateur a d'abord subi de grosses lenteurs sur son serveur, puis a découvert en cherchant la cause un pic de visiteurs uniques sur le dashboard Cloudflare. Aucune campagne publicitaire n'aurait pu expliquer une hausse pareille : le nombre de visiteurs uniques avait été multiplié par dix à vingt. D'où la question posée : d'où vient ce trafic ?

Graphique Cloudflare des visiteurs uniques sur sept jours : une référence autour de 45 000 par jour, puis un pic au-dessus de 800 000
Visiteurs uniques sur le dashboard Cloudflare, du 2 au 9 septembre. La référence quotidienne tourne autour de 45 000 ; le pic dépasse 800 000.
Jour    Requêtes   Pages vues   Visiteurs uniques
J-4       5,33 M     443 219              43 309
J-3       5,23 M     429 719              44 173
J-2       4,96 M     409 933              64 532
J-1       2,37 M     230 294              47 137
J         3,27 M     208 304             816 627
J+1       6,28 M     444 606             721 136
J+2       6,03 M     460 856             522 820
J+3       5,48 M     420 887             378 014

Le diagnostic tient presque entier dans la forme de ces trois colonnes. Les visiteurs uniques sont multipliés par huit à dix-neuf selon le jour comparé. Les pages vues, elles, ne bougent pas, et le jour J elles baissent même. Un trafic qui arrive sans générer de pages vues est un trafic qui ne navigue pas : il récupère une page, il repart, il revient sous une autre adresse. La série horaire a ensuite daté la bascule au créneau près, de 30 223 requêtes à 02h00 UTC à 80 908 à 03h00, sans jamais redescendre ensuite.

Pourquoi les lectures habituelles ne montraient rien

Trois choses distinctes expliquent que cette vague d'attaque ait pu passer inaperçue, et ce qu'elle coûtait réellement. Chacune est assez répandue pour mériter d'être nommée.

  • Les réseaux sources étaient représentatifs de l'audience. Sur un échantillon de quatre heures, les principaux réseaux sources étaient un opérateur vietnamien, trois fournisseurs d'accès grand public français, un opérateur roumain et portugais, un fournisseur émirati, un opérateur historique marocain, un opérateur brésilien, un pakistanais, un turc et un mexicain, les hébergeurs cloud comme Google, Microsoft et Amazon ne venant qu'ensuite. Autrement dit, la répartition géographique du trafic ressemblait à celle de l'audience réelle du site. Les pools de bots résidentiels sont vendus précisément pour qu'une lecture par pays ou par ASN ne retourne rien.
  • Des attaques étaient déjà interceptées, et déjà expliquées. La zone subissait déjà pas mal d'attaques et de requêtes abusives : une règle personnalisée contre le scraping avait intercepté plus de 1,5 million de requêtes en deux jours. Voir un grand nombre monter sur un compteur censé monter n'est pas une alerte. Cela se lit comme la protection qui fait son travail, pas comme la preuve qu'une seconde vague passe à côté.
  • Le serveur Origin, justement, s'était écroulé. Les lenteurs qui ont alerté l'administrateur venaient de là : l'origine a encaissé de plein fouet les premières vagues avant que le cache edge n'en absorbe l'essentiel, comme on le voit plus bas. Mais le problème de fond dépassait la charge serveur : les bots scrappaient les prix du catalogue, un véritable espionnage commercial dont l'impact économique pour l'administrateur était très important. C'est ce dommage-là, bien plus que la montée en charge, qui faisait l'urgence.

Notre méthode d'analyse a rendu la vague de scraping visible

Plutôt que de découper par pays ou par réseau, nous avons croisé quatre dimensions dans une seule requête : le navigateur tel que Cloudflare le reconnaît, la version du protocole HTTP, la version TLS et la source de la classification bot de Cloudflare. Sur une fenêtre de quatre heures, restreinte aux requêtes qui atteignaient réellement l'origine :

191 225  Chrome  HTTP/1.1  TLSv1.3  heuristics
 10 966  Chrome  HTTP/1.1  TLSv1.3  machine_learning
  6 944  Chrome  HTTP/2    TLSv1.3  machine_learning

La même fréquentation aux mêmes heures, la veille, donnait 4 444 requêtes. Un facteur quarante-trois, en une ligne. Trois éléments convergent vers la même lecture.

  • Une contradiction protocolaire. Un vrai Chrome négocie HTTP/2 via ALPN face à un edge Cloudflare et ne redescend pas spontanément en HTTP/1.1. « Chrome en HTTP/1.1 » est la signature d'une bibliothèque HTTP, qu'il s'agisse de Go net/http, de Python requests, de curl ou de node-fetch, portant une chaîne User-Agent de Chrome.
  • Une classification déterministe. Cloudflare étiquetait ce trafic en heuristics, c'est-à-dire correspondance avec une signature d'automatisation connue, et non en machine_learning, qui est le scoring probabiliste appliqué au trafic ambigu. L'edge avait déjà reconnu la forme ; rien n'était configuré pour en tirer une conséquence.
  • Un pool de user agents codé en dur. Quatorze chaînes distinctes, portant chacune entre 14 059 et 14 451 requêtes. Du vrai trafic n'est jamais réparti aussi platement ; trois pour cent d'écart sur quatorze valeurs, c'est un parcours cyclique dans une liste. Les versions annoncées étaient anciennes et incohérentes entre elles, Chrome 99 à 116 sur Windows et 119 à 136 sur Mac, alors que le trafic légitime du site était dominé par Chrome 152.

Un second discriminant conforte la lecture : le referer. En restreignant la même fenêtre à Chrome en HTTP/1.1 classé heuristics, puis en regroupant par host de referer :

191 228  aucun referer   (99,99 %)
     14  referer présent

Une navigation réelle vers une page profonde de catalogue porte presque toujours un referer, interne ou externe. Ce pool n'en envoyait jamais. C'est important pour la suite : le signal referer survit à un passage en HTTP/2, ce que la clause protocolaire ne fait pas.

Il n'est pas exempt de faux positifs, puisqu'il attrape aussi l'entrée directe par saisie d'URL ou favori, mesurée à environ 3 500 requêtes sur vingt heures sur cette zone. C'est exactement pour cela qu'on le mesure avant de l'appliquer.

Une activité constante jour et nuit, puis transférée dans le cache

Trois jours après le début de la vague de scraping, le scraping tournait toujours, mais il n'avait plus le même profil selon l'endroit d'où on l'observait.

Sur le trafic total, le trafic suspect restait constant : environ 25 000 requêtes par heure de jour comme de nuit, 471 370 requêtes sur la journée, soit 8,5 % du trafic de la zone, sans trace de cycle jour/nuit, avec un creux inexpliqué entre 09h00 et 12h00 UTC. Le même jour, le trafic légitime variait d'un facteur six entre son creux de 02h00 et son pic de 14h00. Une courbe qui ignore celui de sa propre audience est de l'automatisation, à elle seule, sans autre preuve.

Série temporelle des requêtes HTTP/1.1 ayant traversé Cloudflare jusqu'au serveur Origin : un plateau autour de 50 000 requêtes par heure du dimanche 6 au lundi 7, puis une chute et un résidu quotidien
Requêtes HTTP/1.1 ayant traversé Cloudflare jusqu'au serveur Origin, soit parce que leur nature de bot n'a pas été détectée, soit parce qu'une règle trop laxiste les a laissé passer. Du 3 au 10 septembre. Le plateau du dimanche 6 au lundi 7 correspond au scraping qui touche le serveur ; ensuite la vague continue, mais l'essentiel des requêtes est servi par le cache.

Sur le trafic qui atteignait réellement le serveur Origin, en revanche, la même courbe s'était effondrée après deux jours de plateau : la majeure partie du scraping était désormais servie par le cache edge, sur des pages déjà demandées. Rien n'avait changé côté attaquant, c'est l'edge qui absorbait la charge.

C'est le point qui piège le plus d'administrateurs : face à des assets statiques (images, CSS, fontes) mis en cache, le scraping peut continuer à pleine vitesse sans jamais faire bouger une seule courbe de supervision serveur.

Deux pièges sur le chemin de la correction

Un rate limiting par URL ne sert à rien ici

Sur les 1 544 207 requêtes que la règle existante avait bloquées en deux jours, les cinquante chemins les plus touchés en représentaient moins de 55 700, soit moins de 4 % du volume.

Le reste était une longue traîne de pages de détail énumérées par identifiant séquentiel sur une plage d'environ 39 000 valeurs, chaque page individuelle se situant à 400 à 430 hits en deux jours.

Chaque URL restait en dessous de tout seuil raisonnablement paramétrable : un rate limiting par URL n'aurait donc rien intercepté du tout. Le comptage doit être agrégé par motif d'URI ou par IP source. C'est la raison la plus fréquente pour laquelle une règle de rate limiting part en production et ne change rien.

Lire les actions avant de lire les volumes

Deux règles du firewall Cloudflare : une règle Block sur une liste d'IP bloquées et une règle Skip sur des wildcards d'URL pour les webservices
Deux règles de la zone vues du dashboard. La règle en Skip porte davantage de trafic que la règle de blocage ; son volume n'en fait ni une faille ni une candidate au resserrement. Les volumes affichés portent sur une autre fenêtre que celles citées dans le texte.

La zone comportait une seconde règle qui correspondait à plus de 1,4 million de requêtes sur les endpoints de l'application mobile. Jugée sur le seul volume, elle ressemble à une masse de trafic non filtré et à une candidate au resserrement.

C'était en réalité une liste d'autorisation Skip délibérée, posée pour que l'application mobile ne rencontre jamais de Managed Challenge. La passer en blocage aurait mis l'application hors service pour tous les clients. Nous l'avions signalée comme candidate en cours d'analyse et avons retiré la suggestion dès que nous avons identifié quelle règle portait quelle action.

Les compteurs d'événements disent ce qu'une règle a intercepté, jamais ce qu'elle en a fait ; sur une zone que l'on n'a pas configurée soi-même, établir l'action de chaque règle vient avant toute proposition d'y toucher.

La recommandation : une superposition de règles, pas une seule

La règle protocolaire arrête cette vague, pas cet attaquant. Le jour où l'attaquant bascule sa bibliothèque en HTTP/2, ce que curl --http2, Python httpx et Go net/http font tous nativement, la contradiction disparaît et la règle n'intercepte plus rien.

D'où une superposition de règles organisée en cinq couches. Chaque couche rend chaque nouvelle adresse IP plus coûteuse, et c'est l'attaquant qui paie ce trafic.

Couche 1, white-list du trafic automatisé légitime

Avant de bloquer quoi que ce soit, on white-liste explicitement les robots que l'on souhaite laisser passer : crawlers des moteurs de recherche (Googlebot, Bingbot), monitoring, application mobile du site. Sans cette couche en tête de liste, chaque règle suivante risque de toucher un automate utile et de créer un faux positif coûteux.

Couche 2, white-list des flux métier

Webhooks et adresses IP des partenaires sont autorisés explicitement, pour la même raison : ce sont des automatisations légitimes qui ne doivent jamais rencontrer un CAPTCHA.

Couche 3, black-list du trafic indésirable

Le blacklisting s'appuie sur tout ce qui identifie le trafic indésirable :

  • sur les user agents : le pool de quatorze chaînes codées en dur, avec des versions annoncées si anciennes (Chrome 99 à 116 sur Windows, 119 à 136 sur Mac) que le résidu légitime est quasi nul. Fragile par construction, le pool pouvant être régénéré en un après-midi, mais gratuit et immédiat ;
  • sur le niveau protocolaire : la contradiction « Chrome en HTTP/1.1 » (http.user_agent contains "Chrome" and http.request.version eq "HTTP/1.1"), en action Managed Challenge plutôt qu'en blocage, car certains proxies d'entreprise rétrogradent effectivement en HTTP/1.1 et un vrai navigateur derrière l'un d'eux résoudra le CAPTCHA là où un client Go échouera. L'application mobile n'est pas concernée, son user agent ne contient aucune chaîne de navigateur, et le trafic Android WebView parle HTTP/2 et échappe à la clause ;
  • sur les URLs : un verrou sur la section ciblée. Les requêtes vers la section énumérée sans cookie cf_clearance reçoivent un Managed Challenge. C'est le levier principal contre la rotation d'IP : chaque nouvelle adresse doit résoudre un CAPTCHA avant de voir la moindre page, ce qui rend très cher un pool de bots résidentiels facturé au trafic. Il faut y ajouter une clause exemptant le cookie de session de l'application, pour que les clients connectés ne rencontrent pas un CAPTCHA dès leur premier clic.

Sur la même zone d'URL, un rate limiting complète la black-list : le comptage porte sur le motif d'URI et non sur l'URL exacte, avec un seuil de quinze à vingt requêtes par IP sur dix minutes, en action Managed Challenge. Un humain consulte quelques offres ; un scraping séquentiel sur des dizaines de milliers d'identifiants franchit ce seuil même à cadence volontairement lente.

Couche 4, Managed Challenge sur les zones non desservies

Pour les zones géographiques et les ASN depuis lesquels le site ne vend rien, un Managed Challenge plutôt qu'un blocage sec : un vrai visiteur égaré résoudra le CAPTCHA là où un bot échouera. Cette vague de scraping particulière échappe à cette couche, ses adresses étant précisément diluées sur des opérateurs grand public des marchés réels du site ; la couche garde pourtant sa place, elle renchérit toute vague moins soignée et coûte peu à entretenir.

Couche 5, rate limiting du trafic automatisé laissé passer

Tout ce qui a été white-listé aux couches 1 et 2 reste soumis à un quota : crawlers IA, partenaires en API, utilisateurs de l'application mobile. L'autorisation dit « vous pouvez passer », le rate limiting dit « pas indéfiniment ». Un crawler IA ou un partenaire légitime tient largement sous un seuil raisonnable ; c'est exactement ce qui distingue un automate toléré d'un bot qui se cache derrière les mêmes signatures.

Sous ces cinq couches, le Super Bot Fight Mode réglé pour intercepter le trafic « definitely automated » sert de filet si tout le reste est contourné.

Et chacune de ces règles part d'abord en action Log, pendant quelques heures, pour mesurer le résidu légitime par rapport à la baseline. On raffine ensuite les expressions si besoin, avant de passer à de véritables actions de blocage. Un changement à la fois, jamais plusieurs, sinon aucun effet ne peut être attribué à sa cause.

Le point qui passait avant toutes les règles

Les pages énumérées se trouvaient sous un chemin de compte (/my-account/) et étaient adressées par un identifiant séquentiel dans l'URL, du type offer-details-5232469.html : la page de détail de l'offre numéro 5 232 469.

Ce montage interroge directement le point classé A01, « Broken Access Control », en tête du Top 10 de l'OWASP : quand une URL désigne une ressource par un identifiant, l'application doit vérifier, à chaque requête, que l'utilisateur authentifié a bien le droit de voir cette ressource. Son absence porte un nom : IDOR, pour Insecure Direct Object Reference. Le cas d'école est un utilisateur qui change l'identifiant dans l'URL et voit la ressource d'un autre : offer-details-5232469.html devient offer-details-5232470.html, et la page s'affiche alors qu'elle concerne le compte de quelqu'un d'autre.

Ici, le soupçon était renforcé par le profil même du scraping : les requêtes partaient de l'extérieur, sans session authentifiée, et reviennent en 200. Deux lectures possibles, toutes deux mauvaises. Soit l'application ne vérifie pas la propriété de l'enregistrement et expose des données de compte à quiconque connaît le schéma d'URL, auquel cas chaque ID de la plage est une fuite en soi. Soit l'application exige bien une authentification, et les 200 ne renvoient qu'une coquille vide, mais alors un bot massivement identifié et classé heuristics par Cloudflare n'a aucune raison de persister : il ne scraperait pas pendant des jours une section qui le renvoie à la porte.

Ce point ne dépend pas du volume de trafic : un attaquant qui ralentit à une requête par minute parcourt quand même tout le catalogue, et aucun filtrage au niveau du bord ne corrige une autorisation manquante côté application. Le vérifier passait donc avant le déploiement de la moindre security rule, et nous l'avons dit avant de proposer les règles. Si ces pages exigent une authentification, le scraping devient sans objet et les couches de filtrage deviennent un confort plutôt qu'une nécessité.

C'est un schéma récurrent qui mérite d'être énoncé clairement. Le filtrage au bord achète du temps et renchérit l'attaque ; il ne remplace pas un contrôle d'autorisation.

Quand un diagnostic met au jour quelque chose que l'application devrait faire à la place, le dire fait partie du travail, même si cela raccourcit la mission.

Ce que ce cas montre

  • Un nombre de visiteurs uniques qui augmente subitement, alors que le nombre de pages vues reste constant, est une signature. Un trafic qui ne navigue pas n'est pas une audience. Avant d'enquêter sur quoi que ce soit d'autre, comparez ces deux courbes.
  • La géographie de la source est la première chose qu'un attaquant dilue. Les lectures par pays et par réseau se déjouent à peu de frais avec un pool de bots résidentiels. La cohérence protocolaire, c'est-à-dire l'accord entre le navigateur annoncé, la version HTTP négociée et les en-têtes réellement envoyés, est bien plus difficile à falsifier et bien plus discriminante.
  • Un serveur Origin calme ne prouve rien. Face à des URLs ou des assets cachables, le scraping est servi par le bord et cesse d'apparaître dans la supervision serveur. Et quand il l'apparaît, ce n'est pas la charge qui coûte le plus cher : ici, le but était la collecte des prix, un espionnage commercial au préjudice direct pour l'administrateur.
  • Mesurer grâce à la fonction Log, puis bloquer. Chaque règle part d'abord en action Log, le temps de mesurer le résidu légitime, puis se raffine avant de passer à de véritables actions de blocage. Le coût d'une security rule mal calibrée est payé par de vrais clients, dans le monde entier, quelques secondes après le déploiement : quelques heures de mesure sont une assurance bon marché.

Si vous observez la même forme sur votre propre zone, les deux guides ci-dessous traitent les parties stables de ce travail sous forme de tutoriel : rate limiting et règles bots sur les endpoints sensibles, et séparer les robots que vous voulez de ceux que vous ne voulez pas.

Vous observez sur votre zone un trafic que vous n'expliquez pas ?

En formule « Do It Yourself », nous analysons votre trafic en accès lecture seule et vous remettons les security rules à déployer, avec les mesures qui les justifient ; en formule « Done for you », nous les déployons et les ajustons nous-mêmes. Tarif fixe, points écrits, garantie de satisfaction.

Choisir une formule

Questions fréquentes

Pourquoi la lecture par pays et par ASN ne montrait-elle rien ?

Parce que la vague de scraping passait par des bots résidentiels à rotation d'adresses, situés dans les marchés réellement desservis par le site. Les opérateurs grand public portaient davantage de trafic que les réseaux de datacenters, si bien que toute lecture géographique ou réseau paraissait plausible. La localisation de la source est la première dimension qu'un attaquant dilue, parce que c'est la moins chère à falsifier. La cohérence protocolaire, elle, est bien plus difficile à maquiller : c'est le croisement navigateur, version HTTP et version TLS qui a rendu la vague lisible en une seule requête.

Pourquoi un user agent de navigateur en HTTP/1.1 est-il un signal fiable ?

Un vrai Chrome négocie HTTP/2 via ALPN face à un edge Cloudflare et ne redescend pas spontanément en HTTP/1.1. Un user agent qui annonce Chrome tout en parlant HTTP/1.1 est presque toujours une bibliothèque HTTP portant une chaîne de navigateur. Le signal n'est pas parfait : certains proxies d'entreprise rétrogradent les connexions, et c'est précisément pour cela que la règle doit appliquer un Managed Challenge plutôt qu'un blocage sec.

Pourquoi un rate limiting par URL échoue-t-il face à un scraper ?

Parce qu'un scraper qui énumère un catalogue répartit ses requêtes sur des dizaines de milliers d'URL distinctes. Ici, les cinquante chemins les plus touchés représentaient moins de quatre pour cent du volume bloqué, et chaque page individuelle restait à quelques centaines de hits sur deux jours, très en dessous de tout seuil raisonnable. Le comptage doit être agrégé par motif d'URI ou par IP source, jamais par URL exacte.

La charge du serveur Origin indique-t-elle qu'on se fait scraper ?

Pas forcément. Ici, les premières vagues ont fait s'écrouler le serveur Origin, et ce sont ces lenteurs qui ont alerté l'administrateur. Mais dès que le cache edge est chaud sur des URLs ou des assets cachables, la plupart des requêtes de scraping sont servies par Cloudflare et n'atteignent plus l'origine : la supervision peut redevenir silencieuse pendant que le contenu continue d'être aspiré. Surtout, l'indicateur qui compte n'est pas la charge serveur mais l'usage fait du contenu : dans ce cas, les bots collectaient les prix, un véritable espionnage commercial à fort impact économique.

Ressources et guides complémentaires