Tutorial API

Parameter Halaman Cloudflare Challenge dan Aliran Token

Saat Cloudflare menyajikan halaman challenge, alur token yang kompleks dimulai — dari parameter halaman awal melalui eksekusi JavaScript hingga cookie qa_validation_cookie akhir. Memahami parameter ini membantu mendiagnosis kegagalan solving, debug alur otomasi, dan memilih pendekatan solving yang tepat.


Anatomi Halaman Challenge

Halaman challenge Cloudflare (HTTP 503) berisi beberapa elemen utama:

<!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>

Parameter kunci

Di halaman tantangan

Parameter Nama Tujuan
ray ID Cloudflare Ray Pengidentifikasi permintaan unik, menghubungkan tantangan dengan permintaan asli
md Metadata tantangan Status tantangan terenkripsi
r Token respons Jawaban yang dihitung (diisi dengan JavaScript)
chl_opt Opsi tantangan Konfigurasi untuk skrip tantangan
cRay Sinar tantangan Sinar sekunder untuk pelacakan tantangan
cZone Zona tantangan ID zona Cloudflare
cUPMDTk Stempel waktu Waktu penerbitan tantangan
cHash Tantang hash Validasi integritas

Di URL skrip tantangan

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123
Komponen Artinya
/cdn-cgi/challenge-platform/ Cloudflare menantang infrastruktur
h/g/ Versi tantangan/variant
orchestrate/ Tantang titik akhir orkestrasi
chl_page/v1 Versi halaman tantangan
ray=ABC123 Minta pengikatan Ray ID

Dalam muatan JavaScript

Skrip tantangan memuat parameter tambahan:

// 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',          // sinyal browser version
    cTTimeMs: '4000',    // Minimum wait time (ms)
    cTplV: 5,            // Template version
    cLt: '...',          // Challenge lifetime
    cRq: {},             // Challenge request data
};

Aliran Token dari Challenge ke Izin

Alur Langkah demi Langkah


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 sinyal browser:

      - 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 sinyal browser + 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?
   - sinyal browser consistent with real browser?
   - No replay (nonce check)?
   ↓

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

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

9. CLOUDFLARE → CLIENT
   HTTP 200 + Protected content

Garis waktu kue

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

Challenge solve:
    → qa_validation_cookie cookie set

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

After ~30 mins: qa_validation_cookie expires
    → Next request triggers new challenge

Cookie Tujuan Lifetime Ruang Lingkup
__cf_bm Tracking sesi bot management 30 menit Domain
qa_validation_cookie Bukti izin challenge 15 menit – 24 jam (dapat dikonfigurasi) Domain
__cflb Afinitas load balancer Sesi Domain
_cfuvid ID pengunjung unik Sesi Domain

Cookie qa_validation_cookie terikat pada:

  1. Alamat IP – Harus dari IP yang sama yang men-solve challenge
  2. User-Agent — Harus cocok dengan UA yang digunakan selama challenge
  3. Domain – Hanya berlaku untuk domain yang menerbitkannya
# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use qa_validation_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

Mengekstraksi Parameter Challenge

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')}")

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);

Solve dengan CaptchaAI

CaptchaAI menangani seluruh aliran token secara internal – Anda tidak perlu mengekstrak parameter challenge secara manual:

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")

Debug Kegagalan Challenge

Titik Kegagalan Umum

Titik Kegagalan Gejala Akar Penyebab
Halaman challenge tidak dimuat Timeout atau respons kosong Masalah jaringan/proxy
Skrip gagal dieksekusi Challenge loop JavaScript API tidak tersedia
Proof-of-work gagal Spinner tanpa batas Timeout komputasi
Respons ditolak Redirect kembali ke challenge Pelanggaran timing atau sinyal browser mismatch
qa_validation_cookie tidak di-set Cookie hilang setelah solve Error parsing respons
qa_validation_cookie ditolak 403 pada request berikutnya IP atau UA mismatch

Checklist 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_validation_cookie
    if cf_clearance_cookie:
        session = requests.Session()
        session.cookies.set("qa_validation_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_validation_cookie rejected",
                "possible_causes": [
                    "Cookie expired",
                    "IP address changed",
                    "User-Agent mismatch",
                    "Cookie from different domain",
                ],
            })

    return steps

Pemecahan Masalah

Gejala Penyebab Solusi
Tipe challenge “managed” tapi solving gagal Challenge perlu Turnstile, bukan JS challenge Coba metode turnstile daripada cloudflare_challenge
qa_validation_cookie berfungsi sekali lalu ditolak Rotasi IP mengubah IP Anda Pin IP untuk durasi izin
Halaman “Just a moment...” tidak pernah resolve JavaScript diblokir atau format salah Gunakan CaptchaAI daripada solving manual
Challenge muncul kembali setiap request qa_validation_cookie tidak terkirim Pastikan cookie persisten dalam sesi
Challenge berbeda di path berbeda Aturan WAF per path Solve untuk setiap path secara terpisah

Pertanyaan Umum

Ini adalah token terenkripsi yang berisi bukti penyelesaian, hash IP, hash UA, dan waktu kedaluwarsa. Anda tidak bisa decode atau memalsukan — hanya edge Cloudflare yang bisa memvalidasinya.

Operator situs mengonfigurasi lifetime-nya. Default 30 menit. Rentangnya 15 menit hingga 24 jam. Pelanggan enterprise bisa menetapkan nilai kustom.

Bisakah saya solve challenge ini tanpa eksekusi JavaScript?

Tidak. Challenge ini memerlukan JavaScript untuk menghitung proof-of-work dan browser sinyal browser. CaptchaAI menangani ini secara internal menggunakan browser nyata.

Apa yang terjadi jika Ray ID berubah?

Setiap request mendapat Ray ID baru. Challenge terikat ke Ray ID saat halaman challenge diterbitkan. Setelah qa_validation_cookie dikeluarkan, Ray ID tidak lagi relevan.

Tidak. qa_validation_cookie dilingkupi oleh domain. Setiap domain memerlukan solve challenge dan cookie izinnya sendiri.


Ringkasan

Halaman challenge Cloudflare berisi Ray ID, skrip challenge, objek opsi, dan parameter form yang mendorong aliran token proof-of-work. Alur tersebut menghasilkan cookie qa_validation_cookie yang terikat pada IP dan User-Agent, valid selama 15 menit hingga 24 jam. Dengan CaptchaAI, Anda tidak perlu parsing parameter ini secara manual — solver menangani seluruh aliran. Untuk debug, memahami parameter membantu mengidentifikasi di mana aliran terputus.

Artikel Terkait

  • Cloudflare Challenge vs Turnstile: Cara Mendeteksi
  • Cloudflare Managed vs Interactive Challenge
  • Cloudflare Turnstile 403 Setelah Token — Perbaikan
Komentar dinonaktifkan untuk artikel ini.