Un défi CAPTCHA Cloudflare n'apparaît jamais au hasard. Derrière chaque widget Turnstile ou page de vérification se cache une règle WAF, écrite par l'opérateur du site, qui a jugé votre requête suspecte selon un critère : adresse IP, pays, chemin, score de bot ou en-têtes. Savoir laquelle se déclenche, et pourquoi, vous permet de diagnostiquer le blocage et de choisir la bonne méthode de résolution.
Quelles actions de règle WAF produisent un CAPTCHA
Une règle WAF peut aboutir à plusieurs actions. Trois présentent un défi résoluble ; les autres laissent passer ou bloquent sans recours :
| Action WAF | Ce qui se passe | Statut HTTP | Méthode CaptchaAI |
|---|---|---|---|
| Managed Challenge | Cloudflare choisit : défi invisible, Turnstile ou JS | 503 | turnstile |
| JS Challenge | Page de défi JavaScript de 5 secondes | 503 | cloudflare_challenge |
| Interactive Challenge | CAPTCHA classique (ancien, obsolète) | 403 | turnstile |
| Block | 403 sec, aucun défi | 403 | N/A (non résoluble) |
| Allow | Laisse passer, aucun contrôle | 200 | N/A |
| Skip | Ignore les règles WAF suivantes | 200 | N/A |
| Log | Journalise l'événement, aucune action | 200 | N/A |
Le Managed Challenge, l'action la plus fréquente
Le Managed Challenge est l'action recommandée par Cloudflare, et la plus répandue. Plutôt que d'imposer un type de défi fixe, il l'adapte visiteur par visiteur :
WAF rule matches → Managed Challenge triggered
↓
Cloudflare evaluates visitor:
├─ Low risk → Invisible pass (no visible challenge)
├─ Medium risk → Turnstile widget (click to verify)
└─ High risk → JavaScript challenge page
↓
Successful → cf_clearance cookie issued
Une même règle peut donc renvoyer un passage invisible un jour, une page JavaScript le lendemain, selon la réputation de votre IP au moment de la requête.
Les modèles de règles WAF qui piègent le trafic automatisé
Les opérateurs écrivent leurs règles avec le langage d'expression de Cloudflare. Trois familles ciblent l'identité de la requête : score de bot, pays et chemin.
Règles de score de bot
- Ce qui déclenche : un score de bot sous le seuil fixé, souvent 30.
- Premier déclencheur pour l'automatisation. Les solveurs de CaptchaAI utilisent de vrais navigateurs, d'où un score proche de l'humain.
# Challenge traffic with low bot scores
(cf.bot_management.score lt 30)
→ Action: Managed Challenge
# Challenge non-verified bots
(cf.bot_management.score lt 50 and not cf.bot_management.verified_bot)
→ Action: JS Challenge
Règles basées sur le pays
- Ce qui déclenche : une IP dans un pays ciblé, ou hors de la liste autorisée.
# Challenge traffic from specific countries
(ip.geoip.country in {"CN" "RU" "VN" "IN"})
→ Action: Managed Challenge
# Block specific regions entirely
(ip.geoip.country eq "XX")
→ Action: Block
Règles basées sur le chemin
- Ce qui déclenche : l'accès à des URL sensibles comme
/login,/signupou/api/.
# Challenge login page access
(http.request.uri.path eq "/login" or http.request.uri.path eq "/signup")
→ Action: Managed Challenge
# Challenge API endpoints
(http.request.uri.path contains "/api/")
→ Action: JS Challenge
Règles de fréquence, d'en-têtes et combinées
Ces trois modèles regardent comment vous requêtez.
Règles basées sur le taux
- Ce qui déclenche : un volume de requêtes trop élevé, souvent couplé au
threat_score.
# Challenge after high request rate
(cf.threat_score gt 10 and http.request.uri.path contains "/search")
→ Action: Managed Challenge
Règles basées sur les en-têtes
- Ce qui déclenche : en-têtes absents ou révélateurs —
Accept-Languagemanquant, User-Agentpythonoucurl.
# Challenge requests with no Accept-Language header
(not http.request.headers["accept-language"])
→ Action: JS Challenge
# Challenge requests with suspicious UA
(http.user_agent contains "python" or http.user_agent contains "curl")
→ Action: Managed Challenge
Règles composées
- Ce qui déclenche : plusieurs conditions réunies (score faible et chemin
/api/et pays hors liste) — un filtre taillé pour le trafic automatisé.
# Multiple conditions
(cf.bot_management.score lt 30
and http.request.uri.path contains "/api/"
and ip.geoip.country ne "US")
→ Action: JS Challenge
Un cas concret côté francophone. Pendant les soldes, un site e-commerce active une règle de fréquence sur /checkout et /search. Vos tests QA, qui interrogeaient l'API de stock chaque seconde depuis une IP OVHcloud, se heurtent alors à un Managed Challenge : c'est le volume et l'origine datacenter qui l'ont déclenché. Côté RGPD, ne journalisez pas plus de données personnelles que nécessaire.
Identifier la règle qui s'est déclenchée
La réponse HTTP porte assez d'indices pour identifier la règle responsable.
À partir des en-têtes HTTP
import requests
def check_cloudflare_rule_info(url):
"""Extract WAF rule information from Cloudflare challenge response."""
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)
info = {
"status": response.status_code,
"cf_ray": response.headers.get("cf-ray", ""),
"cf_cache_status": response.headers.get("cf-cache-status", ""),
"server": response.headers.get("server", ""),
}
# Challenge-specific info
html = response.text
if response.status_code == 503:
if "jschl" in html:
info["challenge_type"] = "JS Challenge (IUAM or WAF rule)"
elif "challenge-platform" in html:
info["challenge_type"] = "Managed Challenge"
elif "cf-turnstile" in html:
info["challenge_type"] = "Turnstile (Managed Challenge)"
elif response.status_code == 403:
if "cf-ray" in str(response.headers):
info["challenge_type"] = "WAF Block (no challenge)"
else:
info["challenge_type"] = "Origin 403 (not Cloudflare)"
return info
Le statut HTTP donne déjà la moitié de la réponse :
- 503 : un défi résoluble, Managed ou JS Challenge.
- 403 avec en-tête
cf-ray: un blocage WAF sec, sans défi. - 403 sans
cf-ray: la réponse vient de l'origine, pas de Cloudflare.
À partir du Ray ID Cloudflare
Chaque réponse Cloudflare porte un en-tête cf-ray. L'opérateur du site peut reporter ce Ray ID dans son tableau de bord (Security > Events) pour voir quelle règle s'est déclenchée. Vous n'avez pas cet accès de l'extérieur, mais noter le cf-ray de chaque défi vous aide à corréler vos observations dans le temps.
Résoudre un défi déclenché par le WAF
Le principe est simple : un Managed Challenge se résout avec la méthode turnstile après extraction du sitekey ; un JS Challenge passe par la méthode cloudflare_challenge avec le sitekey managed. Vous envoyez la tâche, puis interrogez le résultat toutes les 5 secondes jusqu'au token.
En Python
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_cloudflare_challenge(url, challenge_type):
"""Solve Cloudflare challenge based on WAF rule action."""
if challenge_type == "managed_challenge":
# Managed Challenge typically renders as Turnstile
method = "turnstile"
sitekey = extract_turnstile_sitekey(url)
elif challenge_type == "js_challenge":
# JavaScript Challenge page
method = "cloudflare_challenge"
sitekey = "managed"
else:
raise ValueError(f"Unknown challenge type: {challenge_type}")
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": method,
"sitekey": sitekey,
"pageurl": 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")
def extract_turnstile_sitekey(url):
"""Fetch page and extract Turnstile sitekey."""
import re
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0",
}
response = requests.get(url, headers=headers, timeout=15)
match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text)
return match.group(1) if match else None
Node.js
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
async function solveWAFChallenge(url, challengeType) {
const method =
challengeType === "js_challenge" ? "cloudflare_challenge" : "turnstile";
const sitekey =
challengeType === "js_challenge" ? "managed" : await extractSitekey(url);
const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method,
sitekey,
pageurl: url,
json: 1,
},
});
const taskId = submit.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId, json: 1 },
});
if (result.data.status === 1) {
return result.data.request;
}
}
throw new Error("Challenge solve timed out");
}
async function extractSitekey(url) {
const response = await axios.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
},
});
const match = response.data.match(/data-sitekey=["']([0-9x][A-Za-z0-9_-]+)["']/);
return match ? match[1] : null;
}
Une fois le token récupéré :
- Réinjectez-le dans la requête vers la page protégée pour obtenir le cookie
cf_clearance. - Il laisse passer vos requêtes suivantes tant que l'IP et le User-Agent ne changent pas.
Dépannage
| Symptôme | Règle WAF probable | Correctif |
|---|---|---|
Défi uniquement sur /login |
Règle de chemin | Résoudre le défi pour ce chemin |
| Défi depuis des IP datacenter | Score de bot ou réputation IP | Proxys résidentiels ou résolution |
| Défi variable selon le pays | Règle de pays | Proxy dans un pays autorisé ou résolution |
| Défi après N requêtes | Règle de taux | Réduire la cadence ou résoudre chaque défi |
| Toujours un JS Challenge | Action JS Challenge (non Managed) | Méthode cloudflare_challenge |
| 403 sans défi | Action Block (non résoluble) | Changer d'IP, d'en-têtes ou de requête |
Suivre les changements de règles WAF
Les opérateurs ajustent régulièrement leurs règles ; chaque modification affecte directement vos automatisations :
| Changement | Effet sur l'automatisation | Comment le détecter |
|---|---|---|
| Règle ajoutée | Un défi sur des chemins qui passaient | Bascules de statut 503/403 |
| Règle supprimée | Le défi disparaît | 200 là où il y avait 503 |
| Action durcie (Managed → Block) | Un défi résoluble devient un blocage sec | 403 au lieu de 503 |
| Action assouplie (Block → Managed) | Le blocage redevient résoluble | 503 avec page de défi |
| Seuil modifié (score 30 → 50) | Davantage de requêtes défiées | Défis plus fréquents |
| Portée de chemin modifiée | D'autres URL concernées | De nouveaux chemins défiés |
Surveiller l'évolution de la protection
import requests
import time
def monitor_cloudflare_protection(urls, interval=3600):
"""Monitor protection changes across URLs."""
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",
}
last_status = {}
while True:
for url in urls:
try:
response = requests.get(
url, headers=headers, timeout=15, allow_redirects=False
)
status = response.status_code
has_challenge = status == 503 or "cf-turnstile" in response.text
current = {"status": status, "challenge": has_challenge}
previous = last_status.get(url)
if previous and current != previous:
print(f"[CHANGE] {url}")
print(f" Before: {previous}")
print(f" After: {current}")
last_status[url] = current
except requests.RequestException as e:
print(f"[ERROR] {url}: {e}")
time.sleep(interval)
Cette sonde horaire vous alerte dès qu'un chemin bascule de 200 à 503 :
- Vous repérez une règle ajoutée ou une action durcie.
- Vous adaptez votre intégration avant une panne en série.
Questions fréquentes
Comment distinguer un Managed Challenge d'un JS Challenge ?
Les deux renvoient un statut 503, mais le HTML diffère : challenge-platform ou cf-turnstile indiquent un Managed Challenge, le marqueur jschl signale un JS Challenge.
CaptchaAI peut-il résoudre un blocage 403 renvoyé par une règle WAF ?
Non. Un 403 issu d'une action Block est un refus sec, sans défi à résoudre. La seule voie consiste à modifier ce qui a déclenché la règle : IP, en-têtes ou schéma de requête. Seuls les 503 exposent un défi exploitable.
Les proxys résidentiels réduisent-ils le nombre de défis ?
Souvent, oui. Beaucoup de règles ciblent la réputation IP ou l'origine datacenter. Un proxy résidentiel dans un pays autorisé abaisse la probabilité d'un défi sans l'éliminer : une règle de chemin ou de taux se déclenche quand même.
Combien de temps le cookie cf_clearance reste-t-il valable ?
Environ 30 minutes après la résolution. Vos requêtes suivantes passent alors sans nouveau défi, à condition de conserver la même IP et le même User-Agent : le cookie est lié à ce couple.
En résumé
- Une règle WAF déclenche un défi selon des conditions configurables : score de bot, pays, chemin, en-têtes ou taux.
- L'action la plus courante, le Managed Challenge, s'affiche en défi invisible, Turnstile ou JavaScript.
- Résolvez ces défis avec CaptchaAI via la méthode
turnstileoucloudflare_challenge. - Un blocage sec 403 ne se résout pas : revoyez votre schéma de requête ou votre IP.