Aller au contenu

Comment contourner un rate limit par rotation d’en-têtes

Comment contourner un rate limit par rotation d’en-têtes

Section intitulée « Comment contourner un rate limit par rotation d’en-têtes »

Un rate limit limite le nombre de requêtes acceptées pendant une période donnée. Lorsqu’il s’appuie sur une valeur que le client peut inventer, modifier ou faire varier, il limite surtout la bonne volonté de l’attaquant, soit exactement rien.

→ Lire l’explication complète de la marmotte

Mécanisme du rate limit bypass par identifiant client

Section intitulée « Mécanisme du rate limit bypass par identifiant client »

Un limiteur de débit associe une requête à une clé, puis incrémente un compteur ou consomme un jeton dans une réserve. La clé peut contenir l’adresse IP (Internet Protocol) source, l’identité authentifiée, la route appelée, le tenant, un identifiant d’API (Application Programming Interface), ou une combinaison de ces attributs.

Le contournement apparaît quand la clé contient un attribut contrôlé par le client sans vérification d’origine. Les coupables habituels sont X-Forwarded-For, X-Real-IP, Client-IP, un en-tête X-Client-ID, un paramètre de requête, un cookie non signé, ou un champ de corps JSON (JavaScript Object Notation). Une requête par valeur suffit alors à créer une infinité logique de clients.

Le défaut le plus courant consiste à faire confiance à X-Forwarded-For depuis Internet. Cet en-tête sert normalement à transporter l’adresse initiale à travers une chaîne de proxy inverse. Hors d’une frontière proxy explicitement définie, il ne prouve rien. Un navigateur, curl, un script ou un grille-pain suffisamment ambitieux peut l’émettre.

Clé vulnérable :
rate_limit_key = request.headers["X-Forwarded-For"]
Requêtes de l’attaquant :
X-Forwarded-For: 198.51.100.1
X-Forwarded-For: 198.51.100.2
X-Forwarded-For: 198.51.100.3

La rotation peut aussi viser des identifiants de session. Si une route de connexion est limitée par identifiant d’utilisateur fourni avant authentification, l’attaquant alterne les adresses de messagerie ciblées. Si une API crée gratuitement une clé avant de la limiter par clé, l’attaquant automatise l’inscription. Le compteur fonctionne parfaitement, juste sur la mauvaise unité de contrôle. Une montre suisse qui chronomètre les moustiques reste une montre suisse.

Le compromis central est net : utiliser une clé fine, par exemple l’identité authentifiée, réduit les faux positifs pour les réseaux partagés, mais oblige à gérer les cas anonymes et les coûts de vérification d’identité. Utiliser l’adresse source réduit la complexité, mais pénalise les utilisateurs derrière un même réseau et se contourne par réseau de machines compromises ou par relais. La recommandation est de combiner les niveaux, pas de chercher une clé magique.

Démonstration reproductible de rotation d’en-têtes

Section intitulée « Démonstration reproductible de rotation d’en-têtes »

L’exemple suivant utilise un service local fictif qui limite POST /login à trois tentatives par minute selon X-Forwarded-For. Il démontre le défaut sans cibler un système tiers. Le service vulnérable peut être simulé par tout proxy ou middleware configuré pour prendre cet en-tête comme clé directement.

Fenêtre de terminal
# Trois requêtes sous la même valeur : le seuil de trois tentatives est atteint.
for i in 1 2 3; do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://127.0.0.1:8080/login \
-H 'X-Forwarded-For: 203.0.113.10' \
-d 'login=alice&password=wrong'
done
# Rotation de la valeur : un limiteur vulnérable traite chaque requête
# comme un nouveau client et laisse passer des tentatives supplémentaires.
for ip in 203.0.113.11 203.0.113.12 203.0.113.13; do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://127.0.0.1:8080/login \
-H "X-Forwarded-For: $ip" \
-d 'login=alice&password=wrong'
done

Le résultat attendu sur une configuration vulnérable est une succession de réponses applicatives normales, par exemple 401, au lieu d’un refus 429 Too Many Requests après le seuil. Un code 401 répété n’est pas rassurant : il indique précisément que la vérification de mot de passe continue à coûter du calcul, du journal et parfois un appel à un fournisseur d’identité.

Tester aussi les formes moins visibles : plusieurs valeurs dans X-Forwarded-For, espaces, IPv4 mappée en IPv6, casse des en-têtes, valeurs invalides, en-têtes dupliqués et ordre variable. Les bibliothèques ne choisissent pas toutes la même occurrence. Un proxy lit parfois la première adresse, l’application la dernière, et le limiteur une troisième parce que la journée manquait de drame.

La démonstration doit être exécutée contre un environnement détenu ou explicitement autorisé. L’objectif est de vérifier une frontière de confiance, pas de mesurer la patience d’un opérateur d’astreinte.

La correction commence par une règle : seul le proxy inverse de confiance peut déterminer l’adresse client transmise à l’application. Le proxy doit supprimer tout en-tête d’adresse reçu du réseau public, puis écrire sa propre valeur. L’application et le limiteur ne doivent accepter ces en-têtes que si la connexion immédiate provient d’une plage d’adresses de proxy déclarée.

Une configuration robuste sépare donc trois faits : l’adresse de la connexion TCP (Transmission Control Protocol) observée par le frontal, la liste des relais de confiance, et l’adresse client reconstruite après validation. Ajouter aveuglément une adresse à une chaîne X-Forwarded-For ne suffit pas si la chaîne initiale reste intacte.

Pour les routes anonymes sensibles, notamment connexion, réinitialisation de secret et création de compte, appliquer au minimum deux limites indépendantes :

  1. Une limite par adresse client reconstruite et validée.
  2. Une limite par cible métier, par exemple compte, adresse de messagerie normalisée ou numéro de téléphone.
  3. Une limite globale par route afin de protéger la capacité totale.
  4. Une limite par empreinte de session seulement si elle est signée côté serveur ou liée à une session existante.

La limite par identité ne remplace pas la limite réseau. Une attaque de pulvérisation de mots de passe répartit une tentative par compte depuis une même adresse ou depuis de nombreuses adresses. La limite par adresse ne remplace pas la limite par identité. Une attaque par force brute sur un compte passe par des relais distincts. Les deux mesures couvrent des axes différents.

Une identité issue d’un JSON Web Token (JWT), jeton signé transportant des informations d’identité, devient une clé acceptable uniquement après validation de la signature, de l’émetteur, de l’audience et de l’expiration. Lire le champ sub d’un jeton non vérifié revient à demander à l’attaquant quel compteur il souhaite consommer. Le marketing appelle parfois cela de la personnalisation.

Les éléments de fédération et de session méritent la même rigueur que les autres contrôles d’accès. La page Identités couvre les dépendances d’architecture associées, tandis que Réseaux aide à placer la frontière de confiance au bon endroit.

Algorithmes de limitation et décisions d’architecture

Section intitulée « Algorithmes de limitation et décisions d’architecture »

Le compteur fixe par fenêtre, par exemple 100 requêtes par minute, est facile à distribuer mais autorise un double pic à la frontière de deux minutes. La fenêtre glissante corrige ce biais au prix d’un état plus coûteux. Le seau à jetons autorise un burst contrôlé puis maintient un débit moyen. Pour une API publique, le seau à jetons est généralement le meilleur choix : il absorbe les rafales légitimes sans offrir un festival gratuit à l’horloge murale.

Le stockage de l’état doit être partagé entre les instances qui répondent à une même route. Un compteur local par pod Kubernetes contourne la politique dès que le répartiteur distribue les requêtes. Une affinité de session peut masquer le défaut, puis le réintroduire au redémarrage ou lors d’un basculement. Le rate limit distribué exige un magasin atomique, avec durée de vie cohérente avec la fenêtre et comportement documenté en cas de panne.

Le choix en cas d’indisponibilité du magasin est une décision de sécurité :

  • Refuser par défaut sur les routes de connexion, d’émission de jeton, de paiement et d’administration.
  • Accepter avec un plafond local très bas sur les routes de lecture peu sensibles, si l’indisponibilité du contrôle est moins grave qu’une indisponibilité applicative.
  • Ne jamais basculer silencieusement vers une clé fournie par le client.

Une limite protège une ressource nommée. Elle ne protège pas contre un déni de service volumétrique qui sature le lien avant le proxy, ni contre des requêtes coûteuses autorisées sous le seuil. Pour cela, prévoir filtrage en bordure, limites de taille, délais, quotas de concurrence et mise en cache. Les failles courantes commencent souvent là : un contrôle isolé reçoit une mission que son modèle ne peut pas remplir.

Surface d’attaque, journalisation et exploitation

Section intitulée « Surface d’attaque, journalisation et exploitation »

Les en-têtes forgés ne sont qu’un vecteur. La rotation d’adresses via proxies ouverts, réseaux d’anonymisation ou machines compromises contourne une politique strictement IP. La création massive de comptes contourne une politique par identité. Le rejeu de jetons valides contourne une politique qui ne distingue pas les opérations coûteuses. La variation d’URI, d’encodage ou de méthode peut contourner une règle attachée à une route mal normalisée.

Journaliser la clé de limitation sous une forme protégée, la décision, la règle déclenchée, la route normalisée et l’identifiant de requête. Ne pas journaliser de jeton brut, de cookie de session ou de secret dans le seul but de déboguer un compteur. Un incident de rate limit ne mérite pas de créer un second incident, plus gras et plus cher.

Les métriques doivent distinguer les refus par règle, les clés distinctes par route, les erreurs du magasin d’état, les basculements dégradés et la latence du limiteur. Une hausse brutale de clés uniques accompagnée d’un débit stable signale souvent une rotation d’identifiants. Une hausse de 429 sur une identité peut signaler une attaque ou un client cassé, les deux cas méritent une alerte différente.

La rotation des secrets de signature, des clés d’API et des jetons de session doit préserver une période de chevauchement contrôlée. Sinon, un déploiement de clés déclenche des rafales de reconnexion, le limiteur les bloque, puis le tableau de bord conclut que la sécurité fonctionne. Techniquement vrai. Opérationnellement, c’est le genre de victoire qui finit dans un compte rendu.

Sans identifiant client fiable, le limiteur compte des valeurs que l’attaquant invente lui-même. Il suffit de faire tourner un en-tête comme X-Forwarded-For à chaque requête pour que le seuil ne soit jamais atteint : le compteur s’appuie sur la bonne volonté du client, c’est-à-dire sur rien. La conséquence directe est que les protections adossées à ce seuil disparaissent. Le bourrage d’identifiants et la recherche de mots de passe par force brute redeviennent illimités sur l’endpoint d’authentification, l’énumération de comptes reprend son cours, et les abus coûteux passent sous le radar puisque chaque requête est comptée comme un visiteur neuf. La limitation existe toujours dans la configuration, elle ne s’applique simplement à personne.

Une limitation de débit compte les demandes reçues par un service pendant une durée donnée. Pour compter, elle doit décider quelles demandes viennent de la même personne ou du même programme. Elle utilise pour cela une information appelée identifiant : une adresse réseau, un compte connecté, une clé donnée à une application, ou plusieurs de ces éléments ensemble.

Le problème commence quand le service utilise une information que la personne qui envoie la demande peut écrire elle-même. Un en-tête est une information ajoutée à une requête. Certains en-têtes peuvent indiquer une adresse réseau, mais ils ne sont fiables que s’ils ont été écrits par un serveur intermédiaire connu. Si le service accepte directement une valeur envoyée depuis Internet, un attaquant peut changer cette valeur à chaque demande. Le compteur crée alors une nouvelle ligne à chaque fois et ne voit jamais que la limite est dépassée.

La correction consiste à faire vérifier l’adresse par un serveur intermédiaire reconnu, puis à limiter à la fois cette adresse et le compte visé. Une limite générale protège aussi le service entier. Si le système qui mémorise les compteurs tombe en panne, les routes sensibles doivent refuser les demandes plutôt que laisser passer un grand nombre d’essais. Il faut enfin surveiller les refus, les erreurs et les changements inhabituels d’identifiants pour détecter un contournement.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.