API-Tutorials

Cloudflare Challenge-Seitenparameter und Token-Fluss

Die Parameter einer Cloudflare-Challenge-Seite sind kein CAPTCHA im klassischen Sinn: Ray-ID, _cf_chl_opt, ein Proof-of-Work und ein zeitlich begrenztes cf_clearance-Cookie greifen so lange ineinander, bis der Cloudflare-Edge Sie durchlässt. Wer den Ablauf kennt, sieht in Minuten, wo er abbricht. Dieser Artikel zerlegt jeden Parameter und folgt dem Token-Flow bis zum Cookie – entlang der drei Fehlerbilder, an denen Automatisierungen typischerweise hängen bleiben:

  • HTTP 503, das nicht weitergeht: Die Challenge-Seite kommt an, wird aber nie aufgelöst.
  • 403 trotz cf_clearance: Das Cookie passt nicht mehr zu IP oder User-Agent.
  • Endlosschleife: Nach jeder Anfrage erscheint erneut eine Challenge.

Aufbau der Cloudflare-Challenge-Seite

Eine Cloudflare-Challenge-Seite antwortet mit HTTP 503 und liefert statt Inhalt ein Gerüst aus wenigen Kernbausteinen:

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

Die wichtigsten Challenge-Parameter

Parameter im Seiten-HTML

Parameter Name Zweck
ray Cloudflare Ray-ID Eindeutige Anfrage-Kennung; verknüpft die Challenge mit der ursprünglichen Anfrage
md Challenge-Metadaten Verschlüsselter Challenge-Status
r Antwort-Token Berechnete Antwort, per JavaScript gesetzt
chl_opt Challenge-Optionen Konfiguration für das Challenge-Skript
cRay Challenge-Ray Sekundäre Ray zur Verfolgung der Challenge
cZone Challenge-Zone Cloudflare-Zonen-ID
cUPMDTk Zeitstempel Ausgabezeitpunkt der Challenge
cHash Challenge-Hash Integritätsprüfung

Parameter in der Challenge-Skript-URL

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123
Komponente Bedeutung
/cdn-cgi/challenge-platform/ Cloudflare-Challenge-Infrastruktur
h/g/ Challenge-Version/Variante
orchestrate/ Endpunkt der Challenge-Orchestrierung
chl_page/v1 Version der Challenge-Seite
ray=ABC123 Ray-ID-Bindung der Anfrage

Parameter in der JavaScript-Payload

Das Challenge-Skript lädt weitere Parameter nach:

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

Der Ablauf Schritt für Schritt


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

In Schritt 6 prüft Cloudflare drei Signale gleichzeitig:

  • Proof-of-Work: Stimmt die Antwort mit der geforderten Schwierigkeit?
  • Timing: Lag die Bearbeitung im erlaubten Zeitfenster?
  • Fingerprint: Wirkt das Signalprofil wie ein echter Browser, ohne Replay?

Zeitlicher Verlauf der 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

Die vier Cloudflare-Cookies

Cookie Zweck Lebensdauer Geltungsbereich
__cf_bm Bot-Management-Sitzung verfolgen 30 Min Domain
cf_clearance Freigabenachweis der Challenge 15 Min – 24 Std (konfigurierbar) Domain
__cflb Load-Balancer-Affinität Sitzung Domain
_cfuvid Eindeutige Besucher-ID Sitzung Domain
  1. IP-Adresse – dieselbe IP, die die Challenge gelöst hat.
  2. User-Agent – dieselbe UA wie während der Challenge.
  3. Domain – nur die ausstellende Domain.

Typische DACH-Fehlerquelle: Wer die Challenge auf einem Hetzner- oder netcup-Server löst, die Folge-Requests aber über einen Residential-Proxy leitet, ändert die IP – und Cloudflare antwortet sofort mit 403. Lösen und Abruf gehören auf dieselbe IP und denselben User-Agent.

DSGVO-Hinweis: IP-Adressen gelten als personenbezogene Daten. Prüfen Sie bei Proxy- und Server-Setups Ihre Rechtsgrundlage und dokumentieren Sie die Datenflüsse.

# ❌ 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

Challenge-Parameter selbst extrahieren

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

Cloudflare Challenge mit CaptchaAI lösen

CaptchaAI übernimmt den kompletten Token-Flow serverseitig – Ray-ID, Proof-of-Work und Timing müssen Sie nicht selbst auslesen:

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

Fehler im Challenge-Flow debuggen

Häufige Fehlerquellen

Fehlerstelle Symptom Ursache
Challenge-Seite wird nicht geladen Timeout oder leere Antwort Netzwerk- oder Proxy-Problem
Skript wird nicht ausgeführt Challenge läuft in Schleife Fehlende JavaScript-APIs
Proof-of-Work schlägt fehl Endloser Spinner Zeitüberschreitung bei der Berechnung
Antwort abgelehnt Weiterleitung zurück zur Challenge Timing-Verletzung oder abweichendes Browser-Signalprofil
cf_clearance nicht gesetzt Cookie fehlt nach dem Lösen Fehler beim Parsen der Antwort
cf_clearance abgelehnt 403 bei Folge-Anfrage IP oder UA stimmen nicht überein

Debugging-Checkliste

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

Typische Probleme und Lösungen

Symptom Ursache Lösung
Challenge-Typ ist „managed“, aber das Lösen schlägt fehl Die Seite erwartet Turnstile, keine JS-Challenge Methode turnstile statt cloudflare_challenge verwenden
cf_clearance funktioniert einmal, dann 403 IP-Rotation hat Ihre IP geändert IP für die Gültigkeitsdauer des Cookies fixieren
Die Seite „Just a moment…“ lädt endlos JavaScript blockiert oder fehlerhaft Statt manuell zu lösen, CaptchaAI einsetzen
Nach jeder Anfrage erscheint erneut eine Challenge cf_clearance wird nicht mitgesendet Cookies über die gesamte Sitzung hinweg persistieren
Unterschiedliche Challenges auf unterschiedlichen Pfaden WAF-Regeln pro Pfad Jeden Pfad separat lösen

Häufige Fragen

Meist stimmen IP-Adresse oder User-Agent nicht mehr mit dem Lösungszeitpunkt überein. Das Cookie ist an beides gebunden; wechselt der Proxy oder rotiert die IP, verwirft der Edge es sofort. Lösen und Folge-Requests gehören in dieselbe Session.

Das legt der Website-Betreiber fest – Standard sind 30 Minuten, möglich ist alles zwischen 15 Minuten und 24 Stunden. Läuft das Cookie ab, löst die nächste Anfrage automatisch eine neue Challenge aus.

Was bedeutet der Challenge-Typ „managed“ für die Lösung?

cType: 'managed' steht für die JavaScript-Challenge mit Proof-of-Work – also die Methode cloudflare_challenge. Schlägt das Lösen fehl, erwartet die Seite oft Turnstile; dann ist turnstile richtig.

Muss ich die Parameter manuell auslesen, um die Challenge zu lösen?

Nein. Die Extraktion in Python oder Node.js hilft beim Debuggen, zum reinen Lösen ist sie nicht nötig: CaptchaAI lädt die Seite, führt das JavaScript aus und gibt den Clearance-Token zurück.

Nein. Das Cookie gilt nur für die ausstellende Domain. Jede Domain braucht ihre eigene Challenge-Lösung und ihr eigenes Clearance-Cookie.

Fazit

Der Token-Flow läuft auf drei Ebenen – daran erkennen Sie beim Debuggen, wo er abbricht:

  • Seite: Ray-ID, _cf_chl_opt und die Formularfelder md und r kommen mit HTTP 503.
  • Skript: Fingerprint, Proof-of-Work und Timing werden berechnet und verschlüsselt zurückgesendet.
  • Cookie: cf_clearance wird an IP und User-Agent gebunden, gültig 15 Minuten bis 24 Stunden.

Wer den Parameter-Wust überspringen will, überlässt Laden, JavaScript-Ausführung und Proof-of-Work CaptchaAI und bekommt direkt einen gültigen Clearance-Token.

Verwandte Artikel

Kommentare sind für diesen Artikel deaktiviert.