Hướng Dẫn API

Thông số trang Cloudflare Challenge và luồng token

Trang Cloudflare Challenge (HTTP 503) là một chuỗi tham số (ray, md, r, chl_opt...) được JavaScript âm thầm tính toán rồi đổi lấy cookie clearance. Nắm rõ từng tham số là cách nhanh nhất để biết request đang kẹt ở đâu — trang không tải, proof-of-work không xong, hay cookie bị từ chối ở request kế tiếp — kèm code Python và Node.js để tự trích xuất, tự debug.

Đọc xong bài này bạn sẽ:

  • Đọc hiểu từng tham số trong HTML, URL script và payload JavaScript của trang thử thách
  • Theo được đường đi đầy đủ của token, từ request đầu tiên đến cookie clearance
  • Tự viết code Python/Node.js trích xuất tham số cho bất kỳ domain nào
  • Biết chính xác lỗi nào gây ra triệu chứng nào khi luồng giải không ra

Cấu trúc HTML của trang Cloudflare Challenge

Khi edge Cloudflare nghi ngờ request, nó trả HTTP 503 kèm trang HTML dưới đây thay vì nội dung thật. Ba mảnh đáng chú ý:

  • Khối challenge-form chứa tham số ẩn md/r
  • Thẻ <script> trỏ tới orchestrate/chl_page — nơi phép tính proof-of-work diễn ra
  • Nội dung hiển thị "Checking if the site connection is secure" trong lúc chờ

Toàn bộ HTML gốc:

<!DOCTYPE html>
<html>
<head>
    <title>Just a moment...</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
    <div id="challenge-stage">
        <div id="challenge-body-text">
            Checking if the site connection is secure
        </div>
        <div id="challenge-spinner">
            <!-- Loading spinner -->
        </div>
    </div>

    <div id="challenge-form" style="display:none">
        <form id="challenge-form" action="/..." method="POST">
            <!-- Hidden parameters -->
            <input type="hidden" name="md" value="...">
            <input type="hidden" name="r" value="...">
        </form>
    </div>

    <script src="/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=...">
    </script>
</body>
</html>

Từng tham số trong luồng nghĩa là gì

Tham số trên trang HTML

  • ray (Cloudflare Ray ID) — định danh request, gắn với challenge gốc
  • md (metadata) — trạng thái thử thách, đã mã hóa
  • r (response token) — đáp án do JavaScript tính và điền vào
  • chl_opt (tùy chọn) — cấu hình script thử thách
  • cRay (ray phụ) — theo dõi riêng cho thử thách
  • cZone (zone ID) — ID zone Cloudflare của domain
  • cUPMDTk (timestamp) — thời điểm phát hành thử thách
  • cHash (hash) — xác thực tính toàn vẹn

Tham số trong URL của script thử thách

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123

Bóc tách từng đoạn: /cdn-cgi/challenge-platform/ là hạ tầng thử thách của Cloudflare, h/g/ là phiên bản/variant, orchestrate/ là endpoint điều phối, chl_page/v1 là phiên bản trang thử thách, còn ray=ABC123 gắn URL này với đúng request đã tạo ra nó.

Tham số trong payload JavaScript

Script thử thách nạp thêm một loạt tham số vào biến toàn cục — đây là phần mà proof-of-work và fingerprint thật sự dùng để tính response token:

// Extracted from obfuscated challenge script
window._cf_chl_opt = {
    cvId: '2',           // Challenge version
    cType: 'managed',    // Challenge type
    cNounce: '...',      // Cryptographic nonce
    cRay: '...',         // Challenge Ray ID
    cHash: '...',        // Challenge hash
    cUPMDTk: '...',      // Timestamp
    cFPWv: 'g',          // Fingerprint version
    cTTimeMs: '4000',    // Minimum wait time (ms)
    cTplV: 5,            // Template version
    cLt: '...',          // Challenge lifetime
    cRq: {},             // Challenge request data
};

Chín bước trên dây, đúng thứ tự xảy ra giữa client và edge Cloudflare:


1. CLIENT → CLOUDFLARE EDGE
   GET /protected-page
   ↓

2. CLOUDFLARE → CLIENT
   HTTP 503 + Challenge page HTML
   Sets: __cf_bm cookie (bot management tracking)
   Contains: ray ID, challenge script URL
   ↓

3. CLIENT (browser)
   Loads challenge script from /cdn-cgi/challenge-platform/...
   ↓

4. CHALLENGE SCRIPT EXECUTES:
   a. Collects browser fingerprint:

      - Canvas hash
      - WebGL renderer
      - Screen dimensions
      - Installed fonts
      - Timezone
      - Language
   b. Runs proof-of-work:

      - Iterates hash computations
      - Must find answer matching difficulty
   c. Computes timing:

      - Enforces minimum wait (cTTimeMs)
      - Records actual timing
   d. Generates response token:

      - Combines fingerprint + PoW answer + timing
      - Encrypts with challenge nonce
   ↓

5. CLIENT → CLOUDFLARE
   POST /cdn-cgi/challenge-platform/h/g/flow/ov1/...
   Body: { r: "encrypted_response", md: "metadata", ... }
   ↓

6. CLOUDFLARE validates:
   - Proof-of-work answer correct?
   - Timing within acceptable range?
   - Fingerprint consistent with real browser?
   - No replay (nonce check)?
   ↓

7. CLOUDFLARE → CLIENT
   HTTP 200 + Set-Cookie: qa_session_cookie=...; path=/; expires=...
   + HTTP redirect to original URL
   ↓

8. CLIENT → CLOUDFLARE
   GET /protected-page
   Cookie: qa_session_cookie=...
   ↓

9. CLOUDFLARE → CLIENT
   HTTP 200 + Protected content

Mấu chốt: bước 4 chạy toàn bộ trong trình duyệt (fingerprint, proof-of-work, timing); Cloudflare ở bước 6 chỉ xác thực kết quả có khớp độ khó và thời gian hợp lý hay không.

Cookie nào sống được bao lâu trong toàn bộ chuỗi trên:

Request 1: No cookies
    → Challenge page (503)
    → __cf_bm cookie set

Challenge solve:
    → qa_session_cookie cookie set

Request 2+: qa_session_cookie + __cf_bm
    → Content served (200)

After ~30 mins: qa_session_cookie expires
    → Next request triggers new challenge
  • __cf_bm — theo dõi phiên cho bot management, sống 30 phút, phạm vi domain
  • qa_session_cookie — bằng chứng đã vượt qua thử thách, sống 15 phút – 24 giờ tùy site cấu hình, phạm vi domain
  • __cflb — ghim request về cùng backend (load balancer), sống theo phiên, phạm vi domain
  • _cfuvid — ID khách truy cập duy nhất, sống theo phiên, phạm vi domain

Cookie qa_session_cookie bị ràng buộc với ba yếu tố của phiên đã giải:

  1. Địa chỉ IP — phải đến từ đúng IP đã giải thử thách đó
  2. User-Agent — phải khớp UA lúc giải
  3. Domain — chỉ hợp lệ cho domain đã phát hành

Đây là lý do phổ biến nhất khiến cookie "vừa lấy xong" bị 403 ở request kế tiếp: đổi proxy giữa chừng, hoặc giải bằng UA khác với UA gửi request thật.

# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use qa_session_cookie from IP B

# ❌ FAILS — UA mismatch
# Solve with Chrome UA, then send requests with Firefox UA

# ✅ WORKS — Same IP + Same UA
session = requests.Session()
session.headers["User-Agent"] = "Mozilla/5.0 ... Chrome/120.0.0.0"
# Use same session for solving and subsequent requests

Tự trích xuất tham số bằng code

Muốn xem chính xác Cloudflare gửi tham số nào cho một domain, đơn giản nhất là tự parse response. Hai đoạn dưới lấy ray, cType, cTTimeMs và script thử thách trực tiếp từ HTML trả về.

Python

import re
import requests

def extract_challenge_params(url):
    """Extract Cloudflare challenge page parameters."""
    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)
    html = response.text

    params = {
        "status_code": response.status_code,
        "cf_ray": response.headers.get("cf-ray", ""),
        "is_challenge": response.status_code == 503,
    }

    if not params["is_challenge"]:
        return params

    # Extract Ray ID from page
    ray_match = re.search(r"ray['\"]?\s*[:=]\s*['\"]([a-f0-9]+)['\"]", html, re.I)
    if ray_match:
        params["ray_id"] = ray_match.group(1)

    # Extract challenge script URL
    script_match = re.search(
        r'src=["\'](/cdn-cgi/challenge-platform/[^"\']+)["\']', html
    )
    if script_match:
        params["challenge_script"] = script_match.group(1)

    # Extract challenge options
    opt_match = re.search(r"_cf_chl_opt\s*=\s*\{([^}]+)\}", html)
    if opt_match:
        opt_text = opt_match.group(1)

        # Parse individual options
        for key in ["cType", "cRay", "cHash", "cTTimeMs", "cvId", "cFPWv"]:
            val_match = re.search(
                rf"{key}\s*:\s*['\"]?([^'\"', }}]+)", opt_text
            )
            if val_match:
                params[key] = val_match.group(1)

    # Extract form parameters
    md_match = re.search(r'name=["\']md["\']\s+value=["\']([^"\']+)["\']', html)
    if md_match:
        params["md"] = md_match.group(1)

    # Extract cookies from response
    params["cookies"] = {
        name: value
        for name, value in response.cookies.items()
    }

    return params

# Usage
params = extract_challenge_params("https://protected-site.com")
if params["is_challenge"]:
    print(f"Challenge type: {params.get('cType', 'unknown')}")
    print(f"Ray ID: {params.get('ray_id', params['cf_ray'])}")
    print(f"Min wait: {params.get('cTTimeMs', '?')}ms")
    print(f"Script: {params.get('challenge_script', 'not found')}")

Bản Node.js làm cùng việc, hữu ích nếu automation stack của bạn đã chạy trên axios/playwright:

Node.js

const axios = require("axios");

async function extractChallengeParams(url) {
  const response = await axios.get(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
      Accept: "text/html,*/*;q=0.8",
    },
    validateStatus: () => true,
    maxRedirects: 0,
  });

  const html = response.data;
  const params = {
    statusCode: response.status,
    cfRay: response.headers["cf-ray"] || "",
    isChallenge: response.status === 503,
  };

  if (!params.isChallenge) return params;

  // Extract challenge script URL
  const scriptMatch = html.match(
    /src=["'](\/cdn-cgi\/challenge-platform\/[^"']+)["']/
  );
  if (scriptMatch) params.challengeScript = scriptMatch[1];

  // Extract challenge type
  const typeMatch = html.match(/cType\s*:\s*['"]?(\w+)/);
  if (typeMatch) params.challengeType = typeMatch[1];

  // Extract timing
  const timeMatch = html.match(/cTTimeMs\s*:\s*['"]?(\d+)/);
  if (timeMatch) params.minWaitMs = parseInt(timeMatch[1]);

  return params;
}

extractChallengeParams("https://protected-site.com").then(console.log);

Để CaptchaAI lo luồng token này

Tự parse tham số như trên tốt cho debug, nhưng tự động hóa proof-of-work và fingerprint trong production là bài toán khác — đổi nhỏ ở cTplV hay logic cHash đủ để phá code tự viết. Một đội QA outsourcing ở TP.HCM theo dõi giá trên nhiều sàn thương mại điện tử mỗi đêm hay gặp đúng cảnh này: script chạy ổn tuần này, tuần sau Cloudflare đổi template và pipeline giải thử thách gãy hàng loạt.

CaptchaAI xử lý nội bộ toàn bộ luồng trên bằng method=cloudflare_challenge — gửi pageurl, polling res.php, nhận thẳng token clearance mà không cần tự bảo trì logic trích xuất:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_cloudflare_challenge(target_url):
    """Solve Cloudflare challenge page — CaptchaAI handles token flow."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "cloudflare_challenge",
        "sitekey": "managed",
        "pageurl": target_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")

# CaptchaAI handles the full flow:
# 1. Loads the challenge page
# 2. Executes JavaScript
# 3. Solves proof-of-work
# 4. Returns clearance token/cookies
token = solve_cloudflare_challenge("https://protected-site.com/login")

CaptchaAI thường trả token trong dưới 15 giây với tỷ lệ giải thành công cao trên challenge dạng "managed" tiêu chuẩn — vẫn nên poll res.php theo vòng lặp như code trên thay vì đặt timeout cố định, vì thời gian thực tế phụ thuộc độ khó Cloudflare áp cho từng request.


Vì sao thử thách của bạn giải không ra

Bảy điểm nghẽn hay gặp nhất, xếp theo triệu chứng dễ nhận ra trước:

  1. Timeout hoặc response rỗng → trang thử thách không tải được → thường là sự cố network/proxy
  2. Lặp vòng thử thách liên tục → script không chạy được → thiếu JavaScript API cần thiết
  3. Spinner quay vô hạn → proof-of-work không xong → hết thời gian tính toán
  4. Bị chuyển hướng ngược về trang thử thách → response bị từ chối → sai lệch timing hoặc fingerprint không khớp
  5. Thiếu cookie sau khi giải xong → cookie clearance không được set → lỗi khi parse response
  6. 403 ở request kế tiếp → cookie clearance bị từ chối → IP hoặc User-Agent không khớp
  7. Loại "managed" nhưng giải mãi không xong → trang thực chất cần Turnstile, thử method turnstile thay vì cloudflare_challenge

Checklist tự debug

def debug_challenge_flow(url, cf_clearance_cookie=None, user_agent=None):
    """Debug the challenge solve flow step by step."""
    ua = user_agent or (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/120.0.0.0"
    )

    steps = []

    # Step 1: Initial request
    response = requests.get(
        url,
        headers={"User-Agent": ua, "Accept": "text/html,*/*;q=0.8"},
        timeout=15,
        allow_redirects=False,
    )
    steps.append({
        "step": "initial_request",
        "status": response.status_code,
        "is_challenge": response.status_code == 503,
        "cf_ray": response.headers.get("cf-ray", ""),
    })

    # Step 2: Test with qa_session_cookie
    if cf_clearance_cookie:
        session = requests.Session()
        session.cookies.set("qa_session_cookie", cf_clearance_cookie)
        session.headers["User-Agent"] = ua

        response2 = session.get(url, timeout=15, allow_redirects=False)
        steps.append({
            "step": "with_clearance",
            "status": response2.status_code,
            "passed": response2.status_code == 200,
        })

        if response2.status_code != 200:
            steps.append({
                "step": "diagnosis",
                "issue": "qa_session_cookie rejected",
                "possible_causes": [
                    "Cookie expired",
                    "IP address changed",
                    "User-Agent mismatch",
                    "Cookie from different domain",
                ],
            })

    return steps

Cách xử lý bốn tình huống hay gặp nhất

Bên cạnh checklist ở trên, đây là cách xử lý nhanh cho các ca thường gặp trong thực tế:

Cookie xoay vòng bị từ chối → ghim một IP cố định trong vòng đời cookie thay vì đổi proxy giữa chừng.

Trang "Just a moment..." không bao giờ qua → JavaScript đang bị chặn hoặc chạy sai định dạng, dùng CaptchaAI thay vì tự dựng trình duyệt để giải.

Thử thách hiện lại ở mỗi request → cookie clearance không được gửi kèm, đảm bảo nó được giữ xuyên suốt session.

Mỗi path trả về một thử thách khác nhau → đó là rule WAF áp riêng theo từng path, phải giải riêng cho từng path chứ không dùng chung một cookie.


Câu hỏi thường gặp

Bốn câu dev hay hỏi nhất về cookie clearance và luồng token của Cloudflare Challenge:

Một token mã hóa, gộp bằng chứng đã giải proof-of-work, hash IP, hash User-Agent và thời điểm hết hạn. Bạn không giải mã hay giả mạo được — chỉ edge Cloudflare mới xác thực.

Vì sao thử thách cứ hiện lại dù mới giải xong?

Ba nguyên nhân hay gặp nhất, xếp theo tần suất:

  • Cookie không được lưu giữa các request trong cùng session
  • Proxy đổi IP giữa lúc giải và lúc dùng cookie
  • User-Agent hai bước không khớp nhau

Cả ba đều bắt nguồn từ ràng buộc IP + UA đã nói ở phần cookie phía trên.

Có cách nào giải thử thách mà không cần chạy JavaScript không?

Không. Proof-of-work và fingerprint trình duyệt bắt buộc chạy JavaScript thật. CaptchaAI xử lý nội bộ bằng trình duyệt thật, nên bạn không cần tự dựng môi trường JS.

Giải Cloudflare Challenge bằng CaptchaAI tốn bao nhiêu thread?

CaptchaAI tính phí theo thread chạy đồng thời, không theo mỗi lần giải. Plan BASIC ($15/tháng, 5 thread) đủ cho cloudflare_challenge quy mô nhỏ.

Cần polling nhiều domain song song thì tăng lên ADVANCE ($90/tháng, 50 thread) trở lên theo tải thực tế.


Tóm lại

  • Cookie clearance ràng buộc IP + User-Agent, sống 15 phút đến 24 giờ tùy cấu hình site
  • Hiểu tham số giúp xác định chính xác luồng gãy ở bước nào khi debug
  • Ở quy mô lớn, để CaptchaAI xử lý trọn luồng qua method=cloudflare_challenge thay vì tự bảo trì logic trích xuất mỗi khi Cloudflare đổi template

Bài viết liên quan

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