深度解析

触发验证码挑战的 Cloudflare WAF 规则

Cloudflare 的 Web 应用程序防火墙 (WAF) 允许站点运营商创建规则,根据特定请求属性(IP 地址、国家/地区、URL 路径、机器人分数、标头或任意组合)触发 CAPTCHA 质询。了解哪些规则会触发挑战有助于您诊断为什么会看到验证码并选择正确的方法来处理它。


生成验证码的 WAF 规则操作

Cloudflare WAF 规则支持多种操作。其中三个提出了可解决的挑战:

WAF行动 会发生什么 HTTP状态 CaptchaAI方法
管理挑战 Cloudflare 决定:隐形、Turnstile 或 JS 挑战 503 turnstile
JS 挑战 5 秒 JavaScript 挑战页面 503 cloudflare_challenge
互动挑战 传统验证码(旧版,已弃用) 403 turnstile
堵塞 硬403,没有挑战 403 N/A(不可解)
允许 通过,不检查 200 N/A
跳过 跳过剩余的 WAF 规则 200 N/A
日志 记录事件,无操作 200 N/A

管理挑战(最常见)

托管挑战是 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_session_cookie cookie issued

常见的WAF规则模式

站点运营商使用 Cloudflare 的表达式语言创建 WAF 规则。这些是最有可能触发自动化流量验证码的模式:

机器人评分规则

# 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

机器人评分规则是自动化工具最常见的触发因素。 CaptchaAI 的 API 求解器会收到人类级别的分数,因为它们使用真实的浏览器。

基于国家/地区的规则

# 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

基于路径的规则

# 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

基于标头的规则

# 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

确定触发了哪个规则

当出现验证码挑战时,您可以从响应中识别触发规则:

来自 HTTP 标头

import requests

def check_cloudflare_rule_info(url):
    """Extract WAF rule information from Cloudflare 验证流程 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 标头。站点操作员可以在 Cloudflare 的仪表板(安全 > 事件)中使用此 Ray ID 来准确查看触发了哪个规则以及采取了哪些操作。


解决WAF引发的挑战

基于挑战类型的策略

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_cloudflare_challenge(url, challenge_type):
    """Solve Cloudflare 验证流程 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 = "cloudflare_challenge"
        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" ? "cloudflare_challenge" : "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;
}

WAF规则变更及其影响

站点运营商经常调整WAF规则。这些变化会影响自动化:

改变 对自动化的影响 如何检测
添加规则 新的挑战出现在有效的道路上 监视 503/403 状态更改
规则已删除 挑战消失 200,之前是 503
行动升级(托管 → 区块) 可解决的挑战变成了难题 403 而不是 503
行动放松(区块 → 托管) 硬块成为可解决的挑战 503 带有挑战页面
阈值已更改(机器人得分 30 → 50) 更多请求受到挑战 增加挑战频率
路径范围已更改 受影响的不同 URL 新路径回归挑战

监控策略

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 规则 处理方式
仅在 /login 上挑战 基于路径的规则 解决该路径的挑战
仅来自数据中心 IP 的挑战 机器人评分或 IP 信誉规则 使用自有服务器基础设施或解决挑战
挑战因国家/地区而异 基于国家/地区的规则 在允许的国家使用代理或解决
N次请求后挑战 基于速率的规则 降低请求率或解决每个挑战
挑战总是 JS(从不旋转门) JS 挑战行动(非托管) 使用cloudflare_challenge方法
403 没有挑战 块动作(不可解) 更改 IP、标头或请求模式

常见问题

我可以查看网站使用哪些 WAF 规则吗?

不会。WAF 规则是站点运营商私有的。您只能从行为中推断规则 - 哪些路径触发挑战、来自哪些 IP 以及出现什么挑战类型。

WAF 规则是否适用于所有 Cloudflare 计划?

自定义 WAF 规则适用于所有付费计划(专业版、商业版、企业版)。免费计划的 WAF 规则有限。但是,所有计划都提供托管挑战,包括免费计划。

WAF规则是否可以针对不同路径触发不同的挑战?

是的。每个WAF规则可以有不同的操作并匹配不同的路径。站点可以对 /login 端点使用托管质询,对 /api/ 端点使用 JS 质询。

网站多久更改一次 WAF 规则?

它有所不同。电子商务网站经常在销售活动期间调整规则。注重安全的网站可能会每周更新规则。大多数网站在初始设置后很少更改规则。

解决 WAF 挑战是否会影响未来的请求?

是的。解决后,qa_session_cookie cookie 允许后续请求在约 30 分钟内无挑战地通过。该 cookie 与您的 IP 和用户代理绑定。


概括

Cloudflare WAF 规则根据可配置的条件触发 CAPTCHA 挑战:机器人得分、国家/地区、路径、标头或速率。最常见的操作是托管挑战,Cloudflare 会自适应地将其呈现为不可见、Turnstile 或 JS 挑战。解决这些问题CaptchaAI根据渲染的内容使用 turnstilecloudflare_challenge 方法。 WAF 规则中的硬块 (403) 无法解决 - 请更改您的请求模式或 IP。

相关文章

该文章已禁用评论。