Buzeli
buzeliSoluções Digitais
Segurança

O WAF que a AWS criou pra você bloqueou seus editores: o default de 300 req/5min do CloudFront

Publicado em 20 de junho de 2026

Ilustração de um firewall com medidor de rate limit no máximo bloqueando usuários legítimos

O sintoma: editores travados no meio do trabalho

Por volta das 21h, os editores de um portal WordPress começaram a receber erros ao salvar posts e subir imagens. Não era "o site caiu" — o site público respondia normal. Era o painel: admin-ajax.php falhando, upload de mídia retornando erro, o editor de blocos congelando no meio de uma edição.

O WAF do CloudFront estava bloqueando os próprios editores. E o pior: era um WAF que ninguém tinha configurado conscientemente.

O default silencioso: a WebACL que a AWS cria por você

Quando você ativa proteção numa distribuição CloudFront pelo console, a AWS pode anexar automaticamente uma WebACL com uma regra de rate limit padrão: 300 requisições por IP a cada 5 minutos, sem nenhum scope-down. O nome dela denuncia a origem — algo como AWS-RateBasedRule-IP-300-CreatedByCloudFront.

300 req / 5 min parece muito até você lembrar como o wp-admin se comporta. O heartbeat do WordPress bate em admin-ajax.php a cada 15–60 segundos. Cada tela de edição carrega 20–40 assets. Dois ou três editores no mesmo escritório saem pelo mesmo IP público (NAT). Em minutos, esse IP compartilhado estoura 300 requisições — e o WAF, que não distingue editor de atacante, derruba todos eles.

Diagnóstico: ler os logs do WAF, não adivinhar

A primeira coisa foi confirmar quem estava sendo bloqueado e por qual regra. Com os logs da WebACL indo para o CloudWatch, o Logs Insights responde em segundos:

Copiar
fields @timestamp, httpRequest.clientIp, terminatingRuleId, httpRequest.uri
| filter action = "BLOCK"
| stats count(*) as blocks by terminatingRuleId
| sort blocks desc

O resultado foi inequívoco: 1.408 bloqueios na janela, sendo 1.284 da regra AWS-RateBasedRule-IP-300-CreatedByCloudFront. Quebrando por IP e URI, os bloqueados eram editores reais — um deles sozinho com 111 bloqueios em admin-ajax.php e media-new.php, mais cinco IPs residenciais brasileiros editando e subindo mídia. Os outros 124 bloqueios? Aí sim era ruído legítimo de bloquear: um scanner de uma faixa de cloud estrangeira fazendo 934 requisições atrás do webshell ALFA, e um ataque a xmlrpc.php vindo de dezenas de IPs distribuídos.

O rate limit estava certo em existir. Estava errado em tratar um editor logado igual a um scanner anônimo.

A armadilha das soluções óbvias

Duas saídas tentadoras, ambas erradas:

Subir o limite (300 → 2.000): enfraquece a proteção contra DDoS e força bruta para todo mundo, e ainda assim um escritório grande atrás de um NAT pode estourar. Você troca um problema por outro.

Mudar a ação para Count: desliga a proteção na prática — o WAF passa a só contar, não bloquear. Resolve o sintoma matando a função.

A pergunta certa não é "qual o limite ideal?". É "esse limite deveria valer para quem está autenticado?". Não deveria.

O fix: scope-down isentando quem tem o cookie de login

WAFv2 permite um ScopeDownStatement dentro do RateBasedStatement: a regra de rate limit só conta as requisições que casam com o scope-down. Envolvendo um NotStatement, invertemos: contar todo mundo EXCETO quem manda o cookie wordpress_logged_in_. Usuário autenticado não é mais contabilizado no limite; anônimo continua protegido pelos 300 req/5min.

Copiar
{
  "Name": "RateLimit-300-anon-only",
  "Priority": 0,
  "Action": { "Block": {} },
  "Statement": {
    "RateBasedStatement": {
      "Limit": 300,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "NotStatement": {
          "Statement": {
            "ByteMatchStatement": {
              "SearchString": "wordpress_logged_in_",
              "FieldToMatch": {
                "Cookies": {
                  "MatchPattern": { "All": {} },
                  "MatchScope": "VALUE",
                  "OversizeHandling": "MATCH"
                }
              },
              "PositionalConstraint": "CONTAINS",
              "TextTransformations": [{ "Priority": 0, "Type": "NONE" }]
            }
          }
        }
      }
    }
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "RateLimit300AnonOnly"
  }
}
Detalhe que custa tempo: ao enviar essa regra via aws wafv2 ... --cli-input-json, o campo SearchString precisa ir em base64 (d29yZHByZXNzX2xvZ2dlZF9pbl8= para wordpress_logged_in_). Pelo console você digita o texto puro; pela CLI com JSON, não.

O segundo achado: uma das distribuições nem tinha log de WAF

Ao replicar o fix para a segunda distribuição, o comando de query falhou com WAFNonexistentItemException: aquela WebACL nunca teve logging configurado. Ou seja: estava bloqueando às cegas, sem nenhum registro de quem. Antes de qualquer regra, ligamos o log:

Copiar
aws wafv2 put-logging-configuration \
  --logging-configuration \
  ResourceArn=arn:aws:wafv2:us-east-1:123456789012:global/webacl/EXAMPLE/EXAMPLEID,\
LogDestinationConfigs=arn:aws:logs:us-east-1:123456789012:log-group:aws-waf-logs-cf

Regra de ouro: WAF sem log é um WAF que você não pode operar. Você não sabe o que ele bloqueia, não consegue distinguir falso positivo de ataque, e descobre os dois do jeito mais caro — pelo cliente reclamando.

Resultado

Copiar
Bloqueios por rate limit (logados)   : 1.284  ->  0   (após 24h, nas duas distribuições)
Editores travados no wp-admin        : sim    ->  não
Proteção contra anônimo (300/5min)   : ativa  ->  ativa (inalterada)
Logging de WAF na 2ª distribuição    : ausente -> habilitado

Em 24 horas, zero bloqueios legítimos nas duas distribuições, e a proteção contra tráfego anônimo seguiu de pé. O scanner do webshell e o ataque ao xmlrpc continuaram sendo barrados — agora só eles.

Lições

1. O default do CloudFront é hostil para WordPress logado. A WebACL auto-criada com 300 req/5min sem scope-down trata editores como atacantes. Se você ativou proteção pelo console, vá verificar se essa regra existe.

2. O fix de rate limit é o scope-down, não o número. Isente o tráfego autenticado (cookie wordpress_logged_in_) via NotStatement e mantenha o limite apertado para anônimos.

3. WAF sem logging é inoperável. Habilite put-logging-configuration antes de confiar em qualquer regra. Sem log, você não distingue editor de scanner.

4. NAT de escritório engana rate limit por IP. Vários editores saem pelo mesmo IP público; 300 req/5min some rápido. AggregateKeyType por IP precisa considerar isso.

Outro caso de rate limit punindo o usuário legítimo (no ALB, revelado pelo Gutenberg) está em 363 mil falsos 429 em um dia — lá o gatilho era outro, mas a lição se repete: rate limit sem contexto de quem é o cliente vira tiro no próprio pé.

Conclusão

Proteção que a nuvem liga sozinha não é proteção configurada — é um default que pode estar otimizado para o caso médio, não para o seu. Um WAF de borda precisa saber a diferença entre um IP anônimo martelando xmlrpc.php e um editor logado salvando um rascunho. O CloudFront não sabe por padrão; você ensina, com um scope-down de uma regra só.