Credential stuffing e-commerce : règles WAF anti-ATO (JA4, seuils par ASN)
Détection credential stuffing e-commerce WAF : fingerprinting JA4, seuils par ASN et réponse graduée pour bloquer les bots sans casser les clients CGNAT.
Un site e-commerce reçoit, chaque nuit, des dizaines de milliers de tentatives de connexion sur /login. Aucune campagne de phishing, aucun mot de passe deviné à la main : des identifiants volés lors d'une fuite tierce, rejoués en masse pour voir lesquels fonctionnent encore. C'est le credential stuffing, et le secteur retail concentre une part disproportionnée de ces attaques, car un compte client compromis donne accès à des cartes enregistrées, des points de fidélité et des données personnelles revendables.
La réponse naïve, un blocage par adresse IP après N échecs, s'effondre face aux flottes de bots modernes. Voici la méthode qui fonctionne réellement, avec les règles à déployer.
Pourquoi le blocage par IP unique ne suffit plus
Les opérateurs de credential stuffing louent des pools de proxies résidentiels : des dizaines de milliers d'adresses IP appartenant à de vrais foyers, via des SDK publicitaires ou des applications mobiles compromises. Chaque tentative de connexion part d'une IP différente, parfois une seule requête par IP avant rotation. Un seuil du type « 5 échecs en 10 minutes par IP » ne se déclenche jamais, puisque aucune IP individuelle ne dépasse une ou deux tentatives.
Ce comportement « low and slow » distribué est la signature du credential stuffing professionnel, à distinguer d'une attaque par force brute classique qui martèle depuis une poignée d'adresses. La contre-mesure ne peut pas reposer sur l'IP seule : il faut regrouper les requêtes appartenant à la même flotte, à un niveau d'agrégation plus large que l'IP.
Fingerprinting JA4/JA4H : regrouper une flotte malgré des IP rotatives
Le fingerprint JA4 (négociation TLS) et JA4H (en-têtes HTTP) identifient la pile logicielle qui émet la requête, indépendamment de l'adresse IP. Un outil d'automatisation (headless Chrome piloté par Puppeteer, client HTTP maison, framework de credential stuffing du type OpenBullet) laisse une empreinte TLS et HTTP stable, même lorsque l'opérateur fait tourner ses requêtes sur des milliers d'IP résidentielles différentes.
Deux requêtes provenant de deux IP totalement distinctes, mais partageant le même JA4, sont très probablement émises par le même outil, donc le même opérateur. C'est ce qui permet de reconstituer une flotte distribuée en un seul groupe logique.
Exemple de journalisation enrichie côté WAF (format simplifié) :
event: login_attempt
timestamp: 2026-07-17T02:14:33Z
src_ip: 88.161.203.44
ja4: t13d1516h2_8daaf6152771_02713d6af862
ja4h: ge11nn05enus_2b7f4a1c8e9d
uri: /login
status: 401Quand des centaines d'IP distinctes convergent vers le même couple JA4/JA4H sur /login, le signal est net : ce n'est plus « un utilisateur qui se trompe de mot de passe », c'est une flotte. Ce regroupement devient la clé d'agrégation pour tous les seuils qui suivent, à la place de l'IP.
Seuils par ASN et par bloc réseau, pas par IP
Même avec le fingerprinting JA4, un second axe d'agrégation est nécessaire : le numéro de système autonome (ASN) et le bloc réseau (CIDR) d'origine. Les pools résidentiels sont, par construction, concentrés sur un nombre limité de fournisseurs d'accès et de plages IP, même quand les IP individuelles changent à chaque requête.
Un seuil raisonnable ressemble à ceci, en logique de règle plutôt qu'en implémentation figée :
# Pseudo-règle : seuil agrégé par ASN sur /login
# Fenêtre glissante de 5 minutes
IF uri == "/login" AND method == "POST"
AND count(requests GROUP BY asn) > 200 IN 5m
AND fail_rate(asn) > 0.85
THEN action = challengeLe seuil par ASN capture ce qu'un seuil par IP unique ne voit jamais : la même IP peut n'apparaître qu'une fois, mais si trois cents requêtes venant du même ASN échouent sur /login en cinq minutes, avec un taux d'échec supérieur à 85 %, l'origine est un pool de bots, pas trois cents clients malchanceux du même fournisseur d'accès.
Corréler l'échec anormal sur /login avec le comportement sur /checkout
Le signal le plus fiable n'est pas un seuil isolé, c'est une divergence de comportement entre deux endpoints du même parcours utilisateur. Un vrai client qui échoue à se connecter retente deux ou trois fois, puis abandonne ou utilise « mot de passe oublié ». Un bot de credential stuffing enchaîne des centaines de tentatives par minute sur /login, taux d'échec proche de 90 à 95 %, sans jamais atteindre /checkout, car son seul objectif est de valider des couples identifiant/mot de passe, pas d'acheter.
La corrélation utile est donc : taux d'échec anormalement élevé sur /login, associé à un ratio de conversion vers /checkout proche de zéro pour le même groupe (JA4 ou ASN). Un trafic légitime, même en pic de soldes, convertit une partie de ses connexions réussies en visites de panier. Un trafic de credential stuffing ne convertit jamais, par nature.
Anti faux positifs : CGNAT et réponse graduée
Le piège classique du seuil par ASN : bloquer un opérateur mobile entier qui fait du CGNAT (Carrier-Grade NAT), où des dizaines de milliers de vrais clients partagent la même poignée d'IP publiques. Un seuil trop agressif transforme une attaque en incident client de grande ampleur.
Trois garde-fous s'imposent :
- Réponse graduée, jamais un blocage sec en première ligne. Le premier niveau de réaction à un dépassement de seuil est un challenge (JavaScript, CAPTCHA), pas un blocage HTTP 403. Un vrai client passe le challenge sans s'en rendre compte ; un bot simple échoue et s'arrête là.
- Mode détection avant mode blocage. Toute nouvelle règle de seuil est déployée en observation pure pendant une période probante (au moins deux semaines, si possible avec un pic de trafic inclus), pour mesurer le volume de faux positifs avant d'activer le blocage.
- Whitelist des ASN mobiles connus. Les grands opérateurs mobiles et FAI identifiés comme CGNAT reçoivent un seuil relevé, pour éviter qu'un pic légitime ne déclenche un blocage de masse.
Exemple avant/après sur un cas réel de configuration trop agressive :
Avant (seuil naïf par IP) : 5 échecs par IP en 10 minutes, blocage immédiat. Résultat : la flotte résidentielle passe sous le seuil (1 à 2 tentatives par IP), zéro détection, et un abonné mobile derrière un CGNAT partagé se retrouve bloqué après que d'autres clients du même bloc ont échoué leur connexion le même jour.
Après (seuil agrégé JA4 + ASN) : la flotte partageant un même JA4 sur 800 IP distinctes dépasse le seuil de fréquence par ASN en quelques minutes et reçoit un challenge CAPTCHA, qu'elle échoue systématiquement. Le client CGNAT légitime, lui, ne partage pas le JA4 de la flotte et reste sous le seuil individuel : aucun impact.
Le moteur : OWASP CRS et logique custom, en détection avant blocage
L'implémentation s'appuie sur le Core Rule Set OWASP comme socle (protections génériques contre l'injection et les anomalies de requête), complété par une couche de règles custom dédiées à la fréquence et au fingerprinting enrichi. Une règle SecRule combine le comptage de fréquence par groupe (ASN, JA4) avec la variable enrichie portant l'empreinte :
SecRule REQUEST_URI "@streq /login" \
"id:100301,phase:2,\
setvar:'ip.login_fail_count=+1',\
expirevar:'ip.login_fail_count=300',\
chain"
SecRule RESPONSE_STATUS "@eq 401" \
"chain"
SecRule TX:JA4_FINGERPRINT "@within %{TX.KNOWN_STUFFING_JA4_LIST}" \
"setvar:'tx.stuffing_score=+10'"
Le paramètre de sensibilité (paranoia level dans la terminologie CRS) est déployé progressivement : détection pure d'abord, journalisation des faux positifs potentiels, puis blocage effectif une fois le taux validé sur trafic réel. Cette discipline, plus que la sophistication de la règle, distingue un WAF efficace d'un WAF que les équipes finissent par désactiver.
Playbook saisonnier : durcir avant les pics
Le credential stuffing suit un calendrier prévisible, calqué sur celui du commerce lui-même. Soldes d'été, Black Friday et fêtes de fin d'année concentrent le volume de comptes actifs, la valeur des paniers moyens, et donc l'appétit des opérateurs de bots pour tester leurs listes avant que les clients ne changent leurs mots de passe.
Trois actions avant chaque pic saisonnier :
- Abaisser temporairement les seuils de déclenchement du challenge : le volume légitime plus élevé rend les anomalies de fréquence plus difficiles à distinguer à seuil constant.
- Surveiller la republication de nouveaux lots d'identifiants volés (credential dumps) dans les semaines précédant le pic : une fuite fraîche entraîne une vague de test contre tous les sites e-commerce, le vôtre inclus.
- Mettre à jour la liste des ASN connus pour l'hébergement de pools résidentiels avant le pic, pas pendant : l'ajustement en pleine charge est le moment où les faux positifs coûtent le plus cher.
En résumé
Bloquer le credential stuffing sans casser les vrais clients demande d'abandonner le seuil par IP unique pour deux axes d'agrégation complémentaires : le fingerprint JA4/JA4H pour identifier la flotte malgré la rotation d'IP, et l'ASN ou le bloc réseau pour capter le volume distribué. La réponse graduée (challenge avant blocage) et une phase de détection pure avant activation protègent les clients légitimes, notamment ceux derrière un CGNAT partagé.
C'est exactement la logique packagée dans le pack WAF ThreatClaw : des règles validées sur trafic réel, seuils par ASN et signatures JA4 tenues à jour, prêtes à être déployées en détection avant blocage sur votre moteur.
Articles liés
Une fuite mémoire NetScaler configuré en IdP SAML rejoue le scénario CitrixBleed : vol de jetons, contournement MFA, DragonForce. Voici le virtual patching WAF.
GPTBot, ClaudeBot et Bytespider aspirent votre contenu et votre bande passante. Comment les bloquer avec des règles WAF sans toucher à Googlebot ni casser votre SEO.
Des règles WAF (OWASP CRS/Coraza) en amont de l'API LLM pour bloquer les payloads SSRF et les tentatives d'injection de prompt avant qu'ils n'atteignent l'application.
Déployer un WAF open source (Coraza, ModSecurity) avec OWASP CRS : paranoia levels, exclusions ciblées par endpoint et tuning anti faux positifs pour PME.