Explainers

Regras WAF da Cloudflare que desencadeiam desafios CAPTCHA

Um CAPTCHA que aparece do nada num site protegido pela Cloudflare quase nunca é aleatório: é uma regra do WAF reagindo a um sinal da requisição. Descobrir qual sinal disparou é o primeiro passo para escolher a estratégia certa.

Este guia mostra quais ações produzem um desafio solucionável, como identificar a regra e como resolver cada tipo com a CaptchaAI.

Quatro sinais respondem pela maioria dos casos:

  • Pontuação de bot — score baixo em cf.bot_management.score
  • País — geolocalização do IP
  • Caminho da URL — rotas como /login ou /api/
  • Taxa de requisições — volume acima do limite

O que acontece quando uma regra do WAF dispara

A Cloudflare WAF tem sete ações possíveis, mas só três geram desafio solucionável. Primeiro as que valem a pena resolver:

Ação do WAF O que o visitante vê Código HTTP Método CaptchaAI
Desafio Gerenciado Cloudflare decide: invisível, Turnstile ou desafio JS 503 turnstile
Desafio JS Página de desafio JavaScript de 5 segundos 503 turnstile_staging
Desafio Interativo CAPTCHA tradicional (legado, em desuso) 403 turnstile

E as quatro que não têm desafio para resolver:

Ação do WAF O que o visitante vê Código HTTP
Bloquear 403 direto, sem desafio 403
Permitir Passa sem verificação 200
Pular Ignora as regras restantes 200
Registrar Grava o evento, sem ação 200

Desafio Gerenciado: a ação mais comum

É a ação recomendada pela Cloudflare: a decisão do tipo de desafio é adaptativa, visitante por visitante:

WAF rule matches → Managed Challenge triggered
    ↓
Cloudflare evaluates visitor:
  ├─ Low risk → Invisible pass (no visible challenge)
  ├─ Medium risk → Turnstile widget (click to verify)
  └─ High risk → JavaScript challenge page
    ↓
Successful → cookie_qa_validacao cookie issued

Nos três casos, o cookie libera a navegação sem novo desafio por um tempo.


Os padrões de regra que mais pegam tráfego automatizado

Seis padrões, escritos na linguagem de expressão da Cloudflare, respondem pela maior parte dos desafios em automação:

Regras por pontuação de bot

O padrão mais comum: a Cloudflare pontua cada visitante de 0 a 100 e desafia quem fica abaixo do limite:

# Challenge traffic with low bot scores
(cf.bot_management.score lt 30)
→ Action: Managed Challenge

# Challenge non-verified bots
(cf.bot_management.score lt 50 and not cf.bot_management.verified_bot)
→ Action: JS Challenge

Solvers de API da CaptchaAI pontuam como tráfego humano por rodarem em navegadores reais — raramente caem nessa regra.

Regras por país

Bloqueiam ou desafiam pela geolocalização do IP:

# Challenge traffic from specific countries
(ip.geoip.country in {"CN" "RU" "VN" "IN"})
→ Action: Managed Challenge

# Block specific regions entirely
(ip.geoip.country eq "XX")
→ Action: Block

Regras por caminho de URL

Miram rotas específicas — login, cadastro, busca, endpoints de API:

# Challenge login page access
(http.request.uri.path eq "/login" or http.request.uri.path eq "/signup")
→ Action: Managed Challenge

# Challenge API endpoints
(http.request.uri.path contains "/api/")
→ Action: JS Challenge

Regras por taxa de requisições

Entram em ação quando o volume de chamadas de um IP passa do limite:

# Challenge after high request rate
(cf.threat_score gt 10 and http.request.uri.path contains "/search")
→ Action: Managed Challenge

Regras por cabeçalho

Reagem a cabeçalhos ausentes ou a User-Agents que soam como script:

# Challenge requests with no Accept-Language header
(not http.request.headers["accept-language"])
→ Action: JS Challenge

# Challenge requests with suspicious UA
(http.user_agent contains "python" or http.user_agent contains "curl")
→ Action: Managed Challenge

Regras compostas

Combinam várias condições na mesma expressão, tornando o gatilho mais específico:

# Multiple conditions
(cf.bot_management.score lt 30
 and http.request.uri.path contains "/api/"
 and ip.geoip.country ne "US")
→ Action: JS Challenge

Como descobrir qual regra disparou o desafio

Antes de decidir a estratégia, confirme qual ação apareceu — a própria resposta HTTP já entrega isso, sem acessar o painel do site alvo.

Pelos cabeçalhos e pelo corpo da resposta

Uma função simples já classifica a maioria dos casos pelo status HTTP e por trechos do HTML:

import requests

def check_cloudflare_rule_info(url):
    """Extract WAF rule information from Cloudflare Turnstile em staging response."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Accept": "text/html,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
    }

    response = requests.get(url, headers=headers, timeout=15, allow_redirects=False)

    info = {
        "status": response.status_code,
        "cf_ray": response.headers.get("cf-ray", ""),
        "cf_cache_status": response.headers.get("cf-cache-status", ""),
        "server": response.headers.get("server", ""),
    }

    # Challenge-specific info
    html = response.text

    if response.status_code == 503:
        if "jschl" in html:
            info["challenge_type"] = "JS Challenge (IUAM or WAF rule)"
        elif "challenge-platform" in html:
            info["challenge_type"] = "Managed Challenge"
        elif "cf-turnstile" in html:
            info["challenge_type"] = "Turnstile (Managed Challenge)"

    elif response.status_code == 403:
        if "cf-ray" in str(response.headers):
            info["challenge_type"] = "WAF Block (no challenge)"
        else:
            info["challenge_type"] = "Origin 403 (not Cloudflare)"

    return info

Pelo Ray ID da Cloudflare

Toda resposta da Cloudflare inclui um cabeçalho cf-ray. A regra é privada do operador, mas o Ray ID ajuda a localizar o evento em Segurança > Eventos no painel. Vale citá-lo ao abrir um chamado de suporte.


Como resolver desafios disparados pelo WAF

Depois de identificar a ação, o envio à API muda em só dois parâmetros: método e sitekey.

Estratégia por tipo de desafio

Em Python, a lógica de decisão fica assim:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_turnstile_staging(url, challenge_type):
    """Solve Cloudflare Turnstile em staging based on WAF rule action."""

    if challenge_type == "managed_challenge":
        # Managed Challenge typically renders as Turnstile
        method = "turnstile"
        sitekey = extract_turnstile_sitekey(url)
    elif challenge_type == "js_challenge":
        # JavaScript Challenge page
        method = "turnstile_staging"
        sitekey = "managed"
    else:
        raise ValueError(f"Unknown challenge type: {challenge_type}")

    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": method,
        "sitekey": sitekey,
        "pageurl": url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }).json()

        if result.get("status") == 1:
            return result["request"]

    raise TimeoutError("Challenge solve timed out")


def extract_turnstile_sitekey(url):
    """Fetch page and extract Turnstile sitekey."""
    import re
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
    }
    response = requests.get(url, headers=headers, timeout=15)
    match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text)
    return match.group(1) if match else None

Node.js

A mesma lógica em Node.js, com axios:

const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveWAFChallenge(url, challengeType) {
  const method =
    challengeType === "js_challenge" ? "turnstile_staging" : "turnstile";
  const sitekey =
    challengeType === "js_challenge" ? "managed" : await extractSitekey(url);

  const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method,
      sitekey,
      pageurl: url,
      json: 1,
    },
  });

  const taskId = submit.data.request;

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId, json: 1 },
    });

    if (result.data.status === 1) {
      return result.data.request;
    }
  }

  throw new Error("Challenge solve timed out");
}

async function extractSitekey(url) {
  const response = await axios.get(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
    },
  });
  const match = response.data.match(/data-sitekey=["']([0-9x][A-Za-z0-9_-]+)["']/);
  return match ? match[1] : null;
}

O que muda quando o site ajusta o WAF

Regras WAF mudam com frequência, e cada ajuste tem efeito observável na automação:

Mudança Efeito na automação Como identificar
Regra adicionada Um caminho liberado passa a exigir desafio Monitore status 503/403
Regra removida O desafio some 200 onde antes vinha 503
Ação endurecida (desafio → bloqueio) Um desafio solucionável vira bloqueio duro 403 no lugar de 503
Ação relaxada (bloqueio → desafio) Bloqueio duro vira desafio solucionável 503 com página de desafio
Limite alterado (pontuação de bot 30 → 50) Mais requisições são desafiadas Desafios ficam mais frequentes
Escopo de caminho alterado Outras URLs são afetadas Caminhos novos retornam desafio

No Brasil isso aparece na Black Friday: e-commerces apertam a pontuação de bot no pico e relaxam depois — um histórico por hora separa regra de instabilidade.

Como monitorar continuamente

Uma rotina simples registra status e desafio por URL a cada hora, rodando perto do público testado (sa-east-1 para tráfego brasileiro) para não confundir latência com mudança de regra:

import requests
import time

def monitor_cloudflare_protection(urls, interval=3600):
    """Monitor protection changes across URLs."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Accept": "text/html,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
    }

    last_status = {}

    while True:
        for url in urls:
            try:
                response = requests.get(
                    url, headers=headers, timeout=15, allow_redirects=False
                )
                status = response.status_code
                has_challenge = status == 503 or "cf-turnstile" in response.text

                current = {"status": status, "challenge": has_challenge}
                previous = last_status.get(url)

                if previous and current != previous:
                    print(f"[CHANGE] {url}")
                    print(f"  Before: {previous}")
                    print(f"  After:  {current}")

                last_status[url] = current

            except requests.RequestException as e:
                print(f"[ERROR] {url}: {e}")

        time.sleep(interval)

Solução de problemas

Cruzando o sintoma com o padrão mais provável, dá para pular direto para a correção:

Sintoma Regra provável Correção
Desafio só em /login Regra por caminho Resolva o desafio para esse caminho
Desafio só em IPs de datacenter Pontuação de bot ou reputação de IP Faça rotação de proxy ou resolva
Desafio muda conforme o país Regra por país Use proxy no país permitido ou resolva
Desafio aparece após N requisições Regra por taxa Reduza a taxa ou resolva cada desafio
Desafio é sempre JS (nunca Turnstile) Ação de Desafio JS (não Gerenciado) Use o método turnstile_staging
403 sem desafio nenhum Ação de bloqueio (não solucionável) Troque IP, cabeçalhos ou padrão de requisição

Antes de aplicar a correção, confirme três coisas:

  • O código HTTP (503 é solucionável, 403 quase sempre não)
  • Se o widget Turnstile aparece no HTML ou é página JS pura
  • Se o Ray ID muda a cada tentativa (regra ativa, não instabilidade)

Perguntas frequentes

Respostas diretas para as dúvidas que mais aparecem depois que o desafio já foi identificado.

O Desafio Gerenciado e o Cloudflare Turnstile são a mesma coisa?

Não exatamente: o Desafio Gerenciado é a ação do WAF; a Cloudflare decide, por visitante, entre passagem invisível, widget Turnstile ou página JavaScript — o Turnstile é só um dos três resultados.

As regras de WAF valem para o plano gratuito da Cloudflare?

Regras personalizadas ficam nos planos pagos (Pro, Business, Enterprise); o Gratuito tem regras limitadas. O Desafio Gerenciado, porém, vale para todos — por isso é o mais comum na prática.

Depois de resolver o desafio, por quanto tempo a automação continua liberada?

Em geral, por cerca de 30 minutos: o cookie fica vinculado ao IP e ao User-Agent, e mudar qualquer um dos dois invalida a liberação antes do prazo.

Preciso trocar de proxy toda vez que um desafio aparece?

Na maioria dos casos, não — resolver o desafio programaticamente basta. Trocar de proxy só ajuda quando o gatilho é reputação de IP ou geolocalização; do contrário, o desafio se repete.


Resumo

As regras WAF da Cloudflare disparam um CAPTCHA por pontuação de bot, país, caminho, cabeçalhos ou taxa. O Desafio Gerenciado é o mais comum: a Cloudflare decide, por visitante, entre invisível, Turnstile ou página JS. Resolva com a CaptchaAI usando turnstile ou turnstile_staging. Bloqueios diretos (403) não têm desafio — ajuste o padrão de requisição ou o IP.

Artigos relacionados

Os comentários estão desativados para este artigo.