دروس API

معلمات صفحة Cloudflare Challenge وتدفق الرمز المميز

صفحة «Just a moment...» التي تعرضها Cloudflare ليست سوى محطة انتظار مؤقتة؛ هدفها الحقيقي إنتاج ملف تعريف الارتباط cf_clearance الذي يفتح لك المحتوى المحمي. وبين ظهور التحدي وصدور هذا الملف تمرّ سلسلة من المعلمات والحسابات: معرّف Ray، ونص التحدي، وإثبات العمل، وبصمة المتصفح. وفهم ما تلتقطه كل خطوة يجعل تشخيص الفشل وإصلاح سكربتات الأتمتة أسرع بكثير. نفكّك هنا هذه المعلمات واحدة تلو الأخرى، ونوضّح أين يتولّى CaptchaAI المسار كاملًا نيابةً عنك.

ما الذي تحتويه صفحة التحدي فعليًا

حين يعترض Cloudflare طلبك ويعرض تحديًا، يردّ بالحالة HTTP 503 وصفحة HTML تبدو بسيطة لكنها تُخفي عناصر تشغيلية أساسية:

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

المعلمات التي تحرّك التحدي

أهم حقول صفحة HTML هو معرّف Ray الذي يربط كل خطوة لاحقة بالطلب الأصلي، ويرافقه عدد من الحقول المشفّرة:

المعلمة الاسم الوظيفة
ray معرّف Ray من Cloudflare معرّف فريد للطلب يربط التحدي بالطلب الأصلي
md البيانات الوصفية للتحدي حالة التحدي بصيغة مشفّرة
r رمز الاستجابة الإجابة المحسوبة التي يملؤها JavaScript
chl_opt خيارات التحدي إعدادات نص التحدي
cRay Ray الثانوي معرّف إضافي لتتبّع التحدي
cZone منطقة التحدي معرّف منطقة Cloudflare
cUPMDTk الطابع الزمني لحظة إصدار التحدي
cHash تجزئة التحدي التحقق من سلامة البيانات

رابط نص التحدي ومكوّناته

لرابط نص التحدي بنية ثابتة، وكل جزء منه له دلالة:

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123
المكوّن الدلالة
/cdn-cgi/challenge-platform/ البنية التحتية لتحديات Cloudflare
h/g/ إصدار التحدي أو نسخته
orchestrate/ نقطة نهاية تنسيق التحدي
chl_page/v1 إصدار صفحة التحدي
ray=ABC123 ربط معرّف Ray بالطلب

حمولة JavaScript وكائن الخيارات

بمجرد تشغيله، يحمّل نص التحدي معلمات إضافية داخل كائن الخيارات:

// 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
};

رحلة الرمز حتى cf_clearance

يتتبّع المخطط التالي الجلسة من أول طلب محظور حتى تسليم المحتوى، مرورًا بإثبات العمل وإصدار ملف التخليص:


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: cf_clearance=...; path=/; expires=...
   + HTTP redirect to original URL
   ↓

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

9. CLOUDFLARE → CLIENT
   HTTP 200 + Protected content

ويبيّن الخط الزمني متى يُضبط كل ملف ومتى تُعاد دورة التحدي بعد انتهاء صلاحيته:

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

Challenge solve:
    → cf_clearance cookie set

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

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

ملفات تعريف الارتباط في نظام Cloudflare

الملف الحاسم في المسار هو cf_clearance؛ أما البقية فتخدم التتبّع وتوزيع الأحمال:

الملف الوظيفة مدة الصلاحية النطاق
__cf_bm تتبّع جلسة إدارة الروبوتات 30 دقيقة النطاق
cf_clearance إثبات اجتياز التحدي 15 دقيقة – 24 ساعة (قابلة للضبط) النطاق
__cflb تقارب موازِن الأحمال الجلسة النطاق
_cfuvid معرّف الزائر الفريد الجلسة النطاق

لماذا يُرفض cf_clearance أحيانًا

ملف cf_clearance مقيّد بثلاثة عناصر يجب أن تبقى ثابتة معًا:

  • عنوان IP — يجب أن تأتي الطلبات من عنوان IP نفسه الذي حلّ التحدي.
  • User-Agent — يجب أن يطابق قيمة الـ UA المستخدمة أثناء التحدي.
  • النطاق — صالح فقط للنطاق الذي أصدره.

مثال عملي: فريق في القاهرة أو الرياض يراقب توافر المنتجات على متجر محمي بـ Cloudflare عبر وكلاء سكنيين. إذا بدّل البروكسي عنوان IP كل بضع دقائق، فسيُحلّ التحدي من عنوان ويُرسَل الطلب التالي من عنوان آخر، فيُرفض cf_clearance بالحالة 403. العلاج تثبيت عنوان IP نفسه طوال عمر ملف التخليص، والتدوير بعد انتهاء صلاحيته فقط.

# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use cf_clearance 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

استخراج المعلمات برمجيًا

تُظهر الأمثلة التالية كيف تُقرأ هذه المعلمات مباشرة من استجابة الحالة 503: معرّف Ray، ورابط نص التحدي، ونوعه.

باستخدام 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);

الحل التلقائي عبر CaptchaAI

الاستخراج اليدوي مفيد للفهم، لكنه هشّ في الإنتاج لأن Cloudflare تُحدّث نص التحدي باستمرار. يتولّى CaptchaAI تدفق الرمز كاملًا داخليًا فلا تستخرج المعلمات بنفسك:

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

تشخيص إخفاق التحدي

عند تعطّل الحل، تقع المشكلة غالبًا في إحدى النقاط الست التالية؛ والربط بين العَرَض والسبب يختصر البحث:

نقطة الفشل العَرَض السبب الجذري
عدم تحميل صفحة التحدي مهلة انتهاء أو استجابة فارغة مشكلة في الشبكة أو الخادم الوسيط
فشل تشغيل النص تكرار حلقة التحدي واجهات JavaScript مفقودة
فشل إثبات العمل دوران لا نهائي للمؤشر تجاوز المهلة الحسابية
رفض الاستجابة إعادة توجيه إلى التحدي مجددًا انتهاك التوقيت أو عدم تطابق البصمة
عدم ضبط cf_clearance غياب الملف بعد الحل خطأ في تحليل الاستجابة
رفض cf_clearance 403 على الطلب التالي عدم تطابق IP أو UA

يمرّ السكربت التالي على تدفق الحل خطوة بخطوة ليكشف أين يتوقف المسار:

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

    return steps

حلول سريعة لأكثر الأعطال شيوعًا

العَرَض السبب الإصلاح
نوع التحدي "managed" لكن الحل يفشل التحدي يتطلب Turnstile وليس تحدي JavaScript جرّب طريقة turnstile بدل cloudflare_challenge
ينجح cf_clearance مرة ثم يُرفض تدوير عنوان IP غيّر عنوانك ثبّت عنوان IP طوال عمر ملف التخليص
صفحة "Just a moment..." لا تُحلّ أبدًا JavaScript محظور أو مشوَّه استخدم CaptchaAI بدل الحل اليدوي
ظهور التحدي بعد كل طلب عدم إرسال cf_clearance تأكّد من استمرار الملفات داخل الجلسة
تحدٍّ مختلف على مسارات مختلفة قواعد WAF لكل مسار حُلّ كل مسار على حدة

أسئلة شائعة

ما الفرق بين ملفَّي __cf_bm و cf_clearance؟

يُضبط __cf_bm مع صفحة التحدي لتتبّع جلسة إدارة الروبوتات ويرافق بداية الرحلة، بينما لا يصدر cf_clearance إلا بعد اجتياز التحدي وهو الذي يمنحك الوصول إلى المحتوى.

لماذا ينجح cf_clearance مرة واحدة ثم أحصل على 403؟

على الأرجح تغيّر عنوان IP أو User-Agent بين الحل والطلب التالي، لأن الملف مقيّد بكليهما. مع البروكسي الدوّار، ثبّت العنوان حتى انتهاء صلاحية الملف.

هل يمكن استخراج الرمز يدويًا دون تشغيل JavaScript؟

لا. يحتاج التحدي إلى تشغيل JavaScript لحساب إثبات العمل وبناء بصمة المتصفح، ولا سبيل لتخطّي ذلك بقراءة الصفحة فقط. يتكفّل CaptchaAI بهذا داخليًا عبر متصفحات حقيقية.

كيف أميّز تحدي JavaScript المُدار عن Turnstile؟

راقب حقل cType في كائن الخيارات. فإن استمر الفشل مع الطريقة cloudflare_challenge رغم أن النوع "managed"، فغالبًا تعتمد الصفحة Turnstile، فجرّب الطريقة turnstile بدلًا منها.

هل يصلح ملف cf_clearance واحد لعدة نطاقات فرعية أو مواقع؟

لا. cf_clearance محصور بالنطاق الذي أصدره، فكل نطاق يحتاج إلى حلّ مستقل وملف تخليص خاص به.

الخلاصة

تحمل صفحات Cloudflare Challenge معرّفات Ray ونصوص التحدي وكائنات الخيارات ومعلمات النموذج، وهي تقود تدفق إثبات العمل الذي ينتهي بإصدار ملف cf_clearance المرتبط بعنوان IP وUser-Agent والصالح من 15 دقيقة إلى 24 ساعة. مع CaptchaAI لا تحتاج إلى تحليل هذه المعلمات يدويًا، إذ يتولّى الحل المسار بأكمله، ويبقى فهمها عند التشخيص أسرع طريق لمعرفة أين ينكسر التدفق.

مقالات ذات صلة

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.