Lorsque Cloudflare présente une page de défi, un flux de jetons complexe commence : des paramètres de page initiaux jusqu'au cookie cf_clearance final en passant par l'exécution de JavaScript. Comprendre ces paramètres vous aide à diagnostiquer les échecs de résolution, à déboguer les flux d'automatisation et à choisir la bonne approche de résolution.
Anatomie de la page de défi
Une page de défi Cloudflare (HTTP 503) contient plusieurs éléments clés :
<!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>
Paramètres clés
Dans la page du défi
| Paramètre | Nom | Objectif |
|---|---|---|
ray |
ID de rayon Cloudflare | Identifiant de demande unique, lie le défi à la demande d'origine |
md |
Métadonnées du défi | État de défi chiffré |
r |
Jeton de réponse | Réponse calculée (remplie par JavaScript) |
chl_opt |
Options de défi | Configuration pour le script de défi |
cRay |
Rayon de défi | Rayon secondaire pour le suivi des défis |
cZone |
Zone de défi | ID de zone Cloudflare |
cUPMDTk |
Horodatage | Heure d'émission du défi |
cHash |
Hachage de défi | Validation de l'intégrité |
Dans l'URL du script de défi
/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123
| Composant | Signification |
|---|---|
/cdn-cgi/challenge-platform/ |
Infrastructure de défi Cloudflare |
h/g/ |
Version du défi/variant |
orchestrate/ |
Point de terminaison de l’orchestration du défi |
chl_page/v1 |
Version de la page du défi |
ray=ABC123 |
Demander la liaison Ray ID |
Dans la charge utile JavaScript
Le script de challenge charge des paramètres supplémentaires :
// 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
};
Flux de jetons du défi à l'autorisation
Déroulement étape par étape
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
Chronologie des cookies
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
Cookies de défi
| Biscuit | Objectif | Durée de vie | Portée |
|---|---|---|---|
__cf_bm |
Suivi des sessions de gestion des robots | 30 minutes | Domaine |
cf_clearance |
Preuve d'autorisation de défi | 15 min – 24 h (configurable) | Domaine |
__cflb |
Affinité de l'équilibreur de charge | Séance | Domaine |
_cfuvid |
Identifiant unique du visiteur | Séance | Domaine |
Contraintes des cookies cf_clearance
Le cookie cf_clearance est lié à :
- Adresse IP – Doit provenir de la même adresse IP que celle qui a résolu le défi
- User-Agent – Doit correspondre à l'UA utilisé pendant le défi.
- Domaine – Valable uniquement pour le domaine qui l'a émis
# ❌ 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
Extraire les paramètres du défi
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')}")
Noeud.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);
Résolution avec CaptchaAI
CaptchaAI gère l'intégralité du flux de jetons en interne : vous n'avez pas besoin d'extraire manuellement les paramètres de défi :
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")
Échecs du défi de débogage
Points de défaillance courants
| Point de défaillance | Symptôme | Cause profonde |
|---|---|---|
| La page du défi ne se charge pas | Délai d'attente ou réponse vide | Problème de réseau/proxy |
| Le script ne parvient pas à s'exécuter | Boucles de défi | API JavaScript manquantes |
| La preuve de travail échoue | Spinner infini | Délai d'attente de calcul |
| Réponse rejetée | Rediriger vers le défi | Violation du timing ou non-concordance des empreintes digitales |
| cf_clearance non défini | Cookie manquant après la résolution | Erreur d'analyse de la réponse |
| cf_clearance rejeté | 403 sur demande ultérieure | Incompatibilité IP ou UA |
Liste de contrôle de débogage
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
Dépannage
| Symptôme | Parce que | Corriger |
|---|---|---|
| Le type de défi est « géré » mais la résolution échoue | Le défi nécessite un tourniquet, pas un défi JS | Essayez la méthode turnstile au lieu de cloudflare_challenge |
| cf_clearance fonctionne une fois, puis rejeté | La rotation IP a changé votre IP | Pin IP pour la durée de vie de l'autorisation |
| La page "Juste un instant..." ne se résout jamais | JavaScript bloqué ou mal formé | Utilisez CaptchaAI au lieu de la résolution manuelle |
| Le défi réapparaît après chaque demande | cf_clearance n'est pas envoyé | Assurez-vous que les cookies sont conservés en session |
| Différents défis sur différents chemins | Règles WAF par chemin | Résoudre pour chaque chemin séparément |
Questions fréquemment posées
Que contient le cookie cf_clearance ?
Il s'agit d'un jeton crypté contenant la preuve de résolution, le hachage IP, le hachage UA et le délai d'expiration. Vous ne pouvez pas le décoder ou le falsifier : seul le Edge de Cloudflare peut le valider.
Combien de temps dure cf_clearance ?
Les opérateurs du site configurent la durée de vie. La valeur par défaut est 30 minutes. La plage est de 15 minutes à 24 heures. Les clients Entreprise peuvent définir des valeurs personnalisées.
Puis-je résoudre le défi sans exécuter JavaScript ?
Non. Le défi nécessite que JavaScript calcule la preuve de travail et l'empreinte digitale du navigateur. CaptchaAI gère cela en interne en utilisant de vrais navigateurs.
Que se passe-t-il si le Ray ID change ?
Chaque requête obtient un nouveau Ray ID. Le défi est lié au Ray ID au moment de la page du défi. Une fois cf_clearance émis, le Ray ID n'est plus pertinent.
Puis-je réutiliser cf_clearance sur différents domaines ?
Non. cf_clearance s'étend au domaine. Chaque domaine nécessite son propre cookie de résolution de défi et son propre cookie d'autorisation.
Résumé
Les pages de défi Cloudflare contiennent des ID Ray, des scripts de défi, des objets d'options et des paramètres de formulaire qui pilotent un flux de jetons de preuve de travail. Le flux produit un cookie cf_clearance lié à l'IP et au User-Agent, valable de 15 minutes à 24 heures. AvecCaptchaAI, vous n'avez pas besoin d'analyser ces paramètres manuellement : le solveur gère l'intégralité du flux. Pour le débogage, la compréhension des paramètres permet d'identifier l'endroit où le flux s'interrompt.
Articles connexes
- Cloudflare Challenge vs détection de tourniquet
- Défi géré et interactif Cloudflare
- Cloudflare Turnstile 403 après la correction du jeton