Explainers

Правила Cloudflare WAF, которые вызывают проблемы CAPTCHA

CAPTCHA на сайте под Cloudflare почти никогда не появляется случайно — её включает конкретное правило WAF: по оценке бота, стране, пути запроса, заголовкам или их комбинации.

Коротко:

  • Только 3 действия WAF дают решаемую задачу, остальные 4 — блок или чистый проход.
  • Managed Challenge — самое частое действие, оно само выбирает тип проверки.
  • Жёсткий 403 (действие Block) не решается в принципе — тут не поможет никакой API.
  • Триггер вычисляется по HTTP-статусу и телу ответа, без доступа к панели сайта.

Действия WAF, которые заканчиваются CAPTCHA

Правила Cloudflare WAF поддерживают несколько действий. Только три из них ведут к решаемой задаче — остальные либо блокируют запрос, либо пропускают его без проверки:

Действие WAF Что происходит HTTP-статус Метод CaptchaAI
Managed Challenge Cloudflare сам выбирает: invisible, Turnstile или JS 503 turnstile
JS Challenge 5-секундная JS-страница 503 turnstile
Interactive Challenge Классическая CAPTCHA (устаревает) 403 turnstile
Block Жёсткий 403 403 — (нерешаемо)
Allow Проходит без проверки 200
Skip Пропускает остальные правила 200
Log Только запись в лог 200

Managed Challenge — самое частое действие

Managed Challenge — рекомендованное самим Cloudflare действие. Он адаптивно подбирает тип проверки под каждого посетителя:

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 → qa_validation_cookie cookie issued

На практике большинство ботов автоматизации попадают в среднюю или верхнюю ветку — invisible-проход достаётся только трафику с очень высокой оценкой бота.


Типичные шаблоны правил WAF

Операторы сайтов пишут правила WAF на языке выражений Cloudflare. Вот шаблоны, которые чаще всего ловят автоматизированный трафик.

Правила по оценке бота

Самый частый триггер для скриптов и headless-браузеров — низкий bot_management.score:

# 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

API-решатели CaptchaAI получают оценки человеческого уровня, потому что задачу решает настоящий браузер, а не эмуляция без рендеринга.

Правила по стране

Геоблок часто мягче, чем кажется, — сначала CAPTCHA, и только отдельно жёсткий бан:

# 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

Правила по пути

Логин и 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

Правила по частоте запросов

Порог обычно завязан не только на частоту, но и на конкретный путь:

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

Правила по заголовкам

Отсутствие или подозрительный User-Agent ловится не хуже оценки бота:

# 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

Комбинированные правила

Реальные конфигурации почти всегда сочетают несколько условий сразу:

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

Как понять, какое правило сработало

Когда вместо страницы приходит CAPTCHA, триггерное правило можно вычислить по самому ответу — по HTTP-статусу и по телу страницы:

import requests

def check_cloudflare_rule_info(url):
    """Extract WAF rule information from страница Cloudflare-защиты в 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

По Cloudflare Ray ID

Каждый ответ Cloudflare несёт заголовок cf-ray. У оператора сайта этот Ray ID виден в панели Cloudflare (Security → Events) — там точно видно, какое правило сработало. У вас как у клиента доступа к этой панели нет, поэтому диагностика идёт от ответа, а не от чужой конфигурации.


Диагностика типичных проблем

  • Проверка только на /login → правило по пути; решайте задачу на этом маршруте.
  • Проверка только с IP дата-центров → оценка бота или репутация IP; резидентный прокси или решение задачи.
  • Проверка зависит от страны → правило по стране; прокси в разрешённой стране.
  • Проверка после N запросов → правило по частоте; снижайте частоту или решайте каждую.
  • Проверка всегда JS, не виджет → сработал JS Challenge, не Managed; метод turnstile.
  • 403 без проверки → сработал Block (нерешаемо); меняйте IP, заголовки, паттерн запроса.

Совет: сохраняйте cf-ray рядом со статусом в логах — по этой паре легче сверяться с оператором сайта.


Как решать задачи, вызванные WAF

Стратегия зависит от того, какой тип проверки отрисован — Managed Challenge обычно приходит как Turnstile-виджет, JS Challenge — как отдельная JS-страница:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_turnstile(url, challenge_type):
    """Solve страница Cloudflare-защиты в 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"
        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

const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveWAFChallenge(url, challengeType) {
  const method =
    challengeType === "js_challenge" ? "turnstile" : "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;
}

Пример из практики

QA-команда из Алматы поставила на staging-логин cf.bot_management.score lt 30 → Managed Challenge — правило почти всегда рендерит Turnstile-виджет. При решении Turnstile до 10 секунд одного потока хватает на 300+ решений в час: для редких прогонов CI достаточно BASIC ($15/мес, 5 потоков), для постоянного мониторинга — STANDARD ($30/мес, 15 потоков). В логи писали только поля, нужные для самой проверки — дисциплина, актуальная и для 152-ФЗ, и для GDPR.


Что происходит при изменении правил WAF

Операторы сайтов регулярно правят правила WAF, и это напрямую бьёт по автоматизации:

  • Правило добавлено — новая проверка на путях, которые раньше работали (503/403 там, где было 200).
  • Правило удалено — проверка исчезает (200 там, где был 503).
  • Ужесточили (Managed Challenge → Block) — решаемая задача становится жёстким блоком (403 вместо 503).
  • Смягчили (Block → Managed Challenge) — блок становится решаемой задачей (503 вместо 403).
  • Изменили порог (score 30 → 50) — под проверку попадает больше запросов.
  • Изменили область пути — проверки появляются на новых маршрутах.

Стратегия мониторинга

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)

Часто задаваемые вопросы

В чём разница между правилом WAF и Bot Fight Mode Cloudflare?

Bot Fight Mode — грубая защита уровня всего сайта, включается тумблером без условий. Правила WAF точечные: оператор сам решает, для каких путей, стран или оценок бота включать проверку. Сайт нередко использует оба механизма сразу, и внешне эффект неотличим.

Сколько потоков CaptchaAI нужно, чтобы закрыть Managed Challenge при 1000 запросах в час?

Зависит от доли запросов, которая реально получает проверку — обычно заметно меньше 100 %, часть трафика проходит invisible-веткой без виджета. При решении Turnstile до 10 секунд один поток закрывает 300+ решений в час, так что чаще всего хватает BASIC (5 потоков) или STANDARD (15 потоков) — считайте от своих цифр, а не от чужого среднего.

Около 30 минут — после этого последующие запросы с тем же IP и User-Agent проходят без повторной проверки. Точное время зависит от настроек сайта, но полчаса — типичное окно для проверок, выданных через Managed Challenge.

Что делать, если WAF вернул жёсткий 403 без проверки?

Значит, сработало действие Block, а не Challenge — CAPTCHA тут решать нечего, потому что её попросту нет. Единственный выход — поменять то, что триггерит правило: IP-адрес, набор заголовков или сам паттерн запроса (частоту, путь, User-Agent).

Работает ли Managed Challenge на бесплатном тарифе Cloudflare?

Да. В отличие от кастомных правил WAF, которые доступны только на платных тарифах (Pro, Business, Enterprise), Managed Challenge включён на всех тарифах Cloudflare, включая бесплатный.


Итог

  • Правила WAF запускают CAPTCHA по оценке бота, стране, пути, заголовкам или частоте запросов.
  • Чаще всего срабатывает Managed Challenge — Cloudflare сам выбирает invisible-проверку, Turnstile или JS-задачу.
  • Решайте их через CaptchaAI методом turnstile.
  • Жёсткий блок (403) нерешаем в принципе — тут помогает только смена паттерна запроса или IP-адреса, а не API.

Похожие статьи

Комментарии для этой статьи отключены.