Aller au contenu

AWS WAF sur ALB — règles managées, blocages par taux et le patron de promotion COUNT-puis-BLOCK

WAF devant l'ALB arrête la campagne de credential-stuffing qui passe à travers le rate limit applicatif. Groupes de règles managées, règles par taux, et la discipline de faire passer chaque nouvelle règle en COUNT pendant une semaine avant promotion à BLOCK.

Un endpoint de login SaaS prend 8000 requêtes par minute pendant quatre-vingt-dix minutes, toutes les six heures, depuis des IPs en rotation distribuées sur 14 ASN. Le rate limiter applicatif rejette la plupart en 429, mais le chemin d’authentification exécute quand même la comparaison bcrypt sur chaque requête qui l’atteint, les connexions RDS se remplissent, et les logins clients commencent à expirer en bordure. La campagne de credential-stuffing tourne avec des patrons de trafic crédibles que le rate limiter applicatif ne peut pas identifier comme malveillants sans brûler des connexions à la base pour le découvrir. Le correctif, c’est AWS WAF sur l’ALB, avec le groupe de règles managées Bot Control et une règle par taux sur le chemin de login. La discipline qui fait que ça fonctionne, c’est le patron de promotion COUNT-puis-BLOCK.

Ce qui se passe

AWS WAF est un filtre niveau requête qui s’assied devant ALB, CloudFront, API Gateway ou AppSync. Les règles évaluent chaque requête et émettent une de trois actions : ALLOW, BLOCK ou COUNT. Le mode COUNT est le critique — il journalise la requête comme si la règle avait matché mais la laisse passer. C’est le filet de sécurité qui empêche une nouvelle règle de trouer le trafic légitime au jour 1.

Les règles WAF viennent en trois catégories :

  • Groupes de règles managées — fournis par AWS et le Marketplace AWS. La baseline noyau :

    • AWSManagedRulesCommonRuleSet (CRS) — règles dérivées d’OWASP contre SQLi, XSS, RFI, entrées génériquement mauvaises.
    • AWSManagedRulesKnownBadInputsRuleSet — patrons d’exploits connus (Log4Shell, désérialisation Java, CVE courantes).
    • AWSManagedRulesAmazonIpReputationList — IPs qu’AWS a observées en activité malveillante active.
    • AWSManagedRulesBotControlRuleSet — CAPTCHA + challenge JS pour les clients non-navigateur ; la couche de discrimination bot-vs-humain.
    • AWSManagedRulesATPRuleSet (Account Takeover Prevention) — règles spécifiques au credential-stuffing, s’intègre à l’endpoint de login.
  • Règles par taux — bloquent les IPs qui dépassent N requêtes dans une fenêtre glissante de 5 minutes. Le scope de la règle peut être tout le web ACL ou un URI spécifique (le chemin de login précisément).

  • Règles personnalisées — filtres spécifiques à l’application (bloquer les requêtes sans User-Agent, exiger un en-tête personnalisé sur le chemin admin, géo-bloquer certains pays).

Le patron d’architecture pour la défense contre le credential-stuffing :

  1. CRS + Known Bad Inputs en mode BLOCK comme plancher — taux de faux positifs très bas, arrêtent les attaques bruyantes.
  2. Bot Control en mode CHALLENGE sur les endpoints d’authentification — émet un challenge CAPTCHA à tout ce qui ne ressemble pas à un vrai navigateur.
  3. ATP en mode BLOCK sur l’endpoint de login — attrape les patrons de credential-stuffing spécifiquement.
  4. Règle par taux scopée à POST /login à un seuil de 100 requêtes / 5 minutes par IP — filet contre tout ce que les couches précédentes manquent.

La raison pour laquelle WAF sur l’ALB fonctionne là où le rate limiter applicatif ne le fait pas : la décision WAF est prise avant que la requête n’atteigne la cible ALB, donc la comparaison bcrypt ne tourne jamais, la connexion à la base n’est jamais ouverte, et le coût par requête bloquée est une fraction du coût d’une requête qui atteint l’app et y est rejetée.

Comment vérifier

L’état d’un web ACL existant :

aws wafv2 list-web-acls --scope REGIONAL --region us-east-1 \
  --query 'WebACLs[].[Name,Id,ARN]' --output table

aws wafv2 get-web-acl --scope REGIONAL --region us-east-1 \
  --name prod-acl --id <id> \
  --query 'WebACL.Rules[].[Name,Action,OverrideAction,Statement.RateBasedStatement.Limit]' \
  --output table

Chaque règle doit avoir soit une Action (règle personnalisée), soit une OverrideAction (groupe de règles managées). Pour les groupes managés, OverrideAction: NONE signifie que les actions par règle du groupe s’appliquent ; OverrideAction: COUNT force tout le groupe en mode COUNT.

Pour vérifier ce qui est réellement bloqué contre compté, interroger les sampled requests :

aws wafv2 get-sampled-requests --scope REGIONAL --region us-east-1 \
  --web-acl-arn arn:aws:wafv2:us-east-1:123:regional/webacl/prod-acl/xxx \
  --rule-metric-name AWSManagedRulesBotControlRuleSet \
  --time-window StartTime=$(date -u -v-1H +%s),EndTime=$(date -u +%s) \
  --max-items 100 \
  --query 'SampledRequests[].[Timestamp,Action,Request.URI,Request.Headers[?Name==`User-Agent`].Value|[0]]' \
  --output table

La colonne Action montre ce que chaque requête a déclenché. Une nouvelle règle en mode COUNT doit accumuler des requêtes comptées proportionnelles au volume que le trafic de production déclencherait ; si la règle est en COUNT et que rien ne matche, la règle est mal configurée. Inversement, si le mode BLOCK montre 30 % de toutes les requêtes, la règle produit des faux positifs à un rythme qui casse les clients.

La correction

Le patron de promotion COUNT-puis-BLOCK, appliqué à chaque nouvelle règle :

  1. Déployer en mode COUNT pendant une semaine métier complète. Régler l’OverrideAction: COUNT (pour les groupes managés) ou Action: { Count: {} } (pour les règles personnalisées). La règle journalise chaque match sans affecter le trafic.

  2. Échantillonner les logs et classifier les matches. Utiliser Athena sur les logs WAF (destination S3) pour la distribution des matches :

    SELECT
      terminatingRuleId,
      httpRequest.uri,
      httpRequest.country,
      COUNT(*) AS hits
    FROM "waf_logs"."prod_acl"
    WHERE day = '2026/05/20'
      AND ruleGroupList[1].ruleId LIKE 'AWS%'
      AND action = 'COUNT'
    GROUP BY 1, 2, 3
    ORDER BY hits DESC
    LIMIT 100;

    Les URIs en tête révèlent si la règle frappe un vrai patron d’attaque ou un chemin de routine (un webhook tiers, un endpoint de health-check, un endpoint de recherche avec des caractères inhabituels). Les faux positifs sont les lignes où l’URI est un que votre app sert légitimement et la requête a l’air normale dans le reste du log.

  3. Ajouter des exclusions pour les règles à faux positif. Un groupe de règles managées vous laisse régler des actions par règle pour surcharger le défaut. Si SizeRestrictions_BODY matche des téléversements de fichiers légitimes :

    {
      "ManagedRuleGroupStatement": {
        "VendorName": "AWS",
        "Name": "AWSManagedRulesCommonRuleSet",
        "RuleActionOverrides": [
          {
            "Name": "SizeRestrictions_BODY",
            "ActionToUse": {"Count": {}}
          }
        ]
      }
    }

    Ou restreindre le scope de tout le groupe de règles à des URIs spécifiques avec ScopeDownStatement, pour que la règle n’évalue que sur les chemins où l’application est vulnérable.

  4. Promouvoir à BLOCK après une semaine COUNT propre. Basculer OverrideAction: NONE et expédier. Surveiller les métriques WAF les 48 premières heures après promotion — BlockedRequests doit correspondre au rythme que CountedRequests montrait en mode COUNT, dans une variance normale.

  5. Garder la liste de règles courte et l’ordre intentionnel. WAF facture par inspection de requête, et un web ACL avec 30 règles a 30 évaluations par requête. L’ordre compte : règles par taux en premier (peu coûteuses), liste de réputation IP en deuxième (lookup peu coûteux), groupes managés du moins au plus spécifique, règles personnalisées en dernier. AWS évalue les règles par ordre de priorité et s’arrête à la première action terminale.

Pour le credential-stuffing spécifiquement, la disposition de règles que nous déployons :

Priorité 10 : Liste réputation IP - BLOCK
Priorité 20 : Par taux - POST /login, 100 req / 5 min par IP - BLOCK
Priorité 30 : Groupe ATP sur /login - BLOCK
Priorité 40 : Bot Control - CHALLENGE sur /login, NONE ailleurs
Priorité 50 : CRS - BLOCK
Priorité 60 : Known Bad Inputs - BLOCK
Priorité 70 : Règles personnalisées (géo-blocage admin, etc.)

La règle par taux à priorité 20 est le filet — même si Bot Control et ATP ratent tous les deux un patron, la limite par IP arrête le volume. Le seuil de 100 requêtes / 5 minutes par IP est généreux pour tout usage légitime de l’endpoint de login (un power user pourrait en faire 10) et serré contre les campagnes de credential-stuffing (les attaques à IPs en rotation peinent à maintenir le volume sous le seuil par IP).

Le workflow de nettoyage des faux positifs sur le premier mois :

  • Revue quotidienne de CountedRequests pour toute règle encore en mode COUNT.
  • Revue hebdomadaire de BlockedRequests pour les pics — une augmentation soudaine de 10x sur les blocs d’une règle signifie d’habitude un changement légitime de patron de trafic (une nouvelle fonctionnalité, une nouvelle intégration partenaire) attrapé par une vieille règle.
  • Revue mensuelle de la liste de règles — retirer les règles qui ont zéro match en 30 jours (la règle couvre soit un patron qui a cessé, soit elle est scopée à un URI qui n’existe plus).

Trois règles sur chaque déploiement WAF que nous menons :

Ne jamais déployer en BLOCK le jour 1. La semaine COUNT n’est pas négociable. Une nouvelle règle qui part en BLOCK et fait tomber une intégration partenaire le même jour est l’échec canonique de déploiement WAF.

Scoper les groupes managés au besoin. Bot Control sur chaque URI casse les clients API qui utilisent légitimement cURL. Bot Control sur /login et /signup seulement arrête les campagnes de credential-stuffing sans bloquer l’API.

WAF protège, l’app limite encore. WAF arrête les attaques bruyantes. Le rate limiter de l’application est la seconde couche pour les patrons qui ressemblent à du trafic — clients abusifs, scripts emballés d’utilisateurs légitimes. Les deux couches existent pour des modèles de menace différents.

Le déploiement initial sur une charge SaaS tient en une journée concentrée pour le web ACL, une semaine d’observation en mode COUNT, une journée de nettoyage des faux positifs. Le coût continu est d’une heure par mois pour la revue des métriques. La campagne qui a déclenché la question s’arrête le jour même où la règle par taux promeut à BLOCK.


Stack Harbor opère le nettoyage IAM, la baseline CloudWatch, AWS Backup et le rythme de contrôle des coûts dans le cadre de Gestion AWS et des Environnements infogérés en continu. Si le trafic de credential-stuffing remplit votre file bcrypt, commencez par une consultation d’infrastructure.