صفحة «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 لا تحتاج إلى تحليل هذه المعلمات يدويًا، إذ يتولّى الحل المسار بأكمله، ويبقى فهمها عند التشخيص أسرع طريق لمعرفة أين ينكسر التدفق.
مقالات ذات صلة
- الفرق بين Cloudflare Challenge وTurnstile وكيفية تمييزهما
- التحدي المُدار مقابل التحدي التفاعلي في Cloudflare
- معالجة خطأ 403 في Cloudflare Turnstile بعد إصلاح الرمز