CAPTCHA vừa xuất hiện trên một trang mà trước đó bạn chạy tự động hóa hoặc test không hề gặp? Nhiều khả năng nguyên nhân không phải do Cloudflare "ghét" bot nói chung, mà do một quy tắc WAF (Web Application Firewall) cụ thể vừa khớp với request của bạn — dựa trên bot score, quốc gia, đường dẫn URL, header, hoặc kết hợp nhiều điều kiện.
Cloudflare WAF hỗ trợ bảy hành động khác nhau, nhưng chỉ ba trong số đó tạo ra thử thách có thể giải được; các hành động còn lại là chặn cứng hoặc cho qua thẳng. Nắm được hành động nào đang chạy giúp bạn chẩn đoán đúng nguyên nhân và chọn phương pháp xử lý phù hợp thay vì đoán mò.
Hành động WAF nào tạo ra CAPTCHA
Quy tắc WAF của Cloudflare hỗ trợ nhiều hành động (action), nhưng chỉ ba trong số đó đưa ra thử thách giải được:
| Hành động WAF | Điều gì xảy ra | Trạng thái HTTP | Phương thức CaptchaAI |
|---|---|---|---|
| Managed Challenge (thử thách được quản lý) | Cloudflare tự quyết định: vô hình, Turnstile, hoặc JS challenge | 503 | turnstile |
| JS Challenge | Trang thử thách JavaScript, xử lý trong khoảng 5 giây | 503 | cloudflare_challenge |
| Interactive Challenge | CAPTCHA truyền thống kiểu cũ, Cloudflare không còn khuyến khích dùng | 403 | turnstile |
| Block | Chặn cứng 403, không có thử thách nào để giải | 403 | Không áp dụng (không giải được) |
| Allow | Cho qua thẳng, không kiểm tra | 200 | Không áp dụng |
| Skip | Bỏ qua các quy tắc WAF còn lại | 200 | Không áp dụng |
| Log | Chỉ ghi log sự kiện, không hành động | 200 | Không áp dụng |
Managed Challenge — hành động phổ biến nhất trong bảng trên — là action Cloudflare khuyến nghị mặc định. Nó không cố định một loại thử thách, mà đánh giá từng khách truy cập rồi quyết định thích ứng:
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 → qa_session_cookie cookie issued
Vì cùng một Managed Challenge có thể hiện ra dưới ba dạng khác nhau tùy điểm rủi ro của bạn, script tự động hóa cần xử lý được cả ba nhánh — chứ không chỉ nhánh Turnstile hay gặp nhất.
Khắc phục nhanh theo triệu chứng
Trước khi đào sâu vào từng loại quy tắc, đây là mấy triệu chứng hay gặp nhất và cách xử lý — hữu ích khi bạn chỉ có log request chứ chưa có quyền xem dashboard Cloudflare của trang đích:
- Chỉ
/loginbị thử thách — nghi ngờ quy tắc theo đường dẫn; giải thử thách riêng cho path đó. - Chỉ IP từ datacenter mới bị thử thách — nghi ngờ quy tắc bot score hoặc IP reputation; đa dạng nguồn request hoặc giải thử thách.
- Thử thách khác nhau theo từng quốc gia — nghi ngờ quy tắc theo quốc gia; dùng proxy ở quốc gia được phép, hoặc giải thử thách.
- Thử thách xuất hiện sau N request — nghi ngờ quy tắc theo rate; giảm tần suất request hoặc giải từng thử thách.
- Luôn là JS Challenge, không bao giờ đổi — hành động đang là JS Challenge chứ không phải Managed; dùng phương thức
cloudflare_challenge. - 403 mà không có thử thách nào — hành động Block, không giải được; cần đổi IP, header hoặc mẫu request.
Nếu bạn rơi vào trường hợp cuối — 403 không kèm thử thách — đừng mất thời gian tìm cách giải. Đó là chặn cứng; không có token nào để lấy cả.
Các mẫu quy tắc WAF thường gặp
Người vận hành trang web viết quy tắc WAF bằng ngôn ngữ biểu thức riêng của Cloudflare. Dưới đây là các nhóm mẫu hay gặp nhất khi bạn chạy tự động hóa hoặc thu thập dữ liệu, xếp theo thuộc tính mà quy tắc kiểm tra.
Theo bot score, quốc gia, đường dẫn, rate, header và điều kiện ghép
Bot score là kiểu quy tắc gây thử thách nhiều nhất cho công cụ tự động hóa:
# 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
Ví dụ: một đội QA tại công ty outsource ở TP.HCM theo dõi giá sản phẩm trên nhiều sàn thương mại điện tử — request từ headless browser cấu hình mặc định thường tụt bot score xuống dưới ngưỡng và bị Managed Challenge chặn, dù trang đích không cố ý nhắm vào họ. Solver dùng browser thật của CaptchaAI nhận điểm ở mức người dùng bình thường vì render trang như trình duyệt thật.
Quy tắc theo quốc gia:
# 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
Quy tắc theo đường dẫn (path):
# 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
Quy tắc theo tần suất request (rate):
# Challenge after high request rate
(cf.threat_score gt 10 and http.request.uri.path contains "/search")
→ Action: Managed Challenge
Quy tắc theo header, ví dụ thiếu Accept-Language hoặc User-Agent trông như thư viện HTTP thay vì trình duyệt:
# 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
Cuối cùng, quy tắc ghép nhiều điều kiện là dạng khó đoán nhất từ bên ngoài vì phải khớp đồng thời nhiều biến:
# Multiple conditions
(cf.bot_management.score lt 30
and http.request.uri.path contains "/api/"
and ip.geoip.country ne "US")
→ Action: JS Challenge
Suy luận ra bằng cách thử đổi từng biến (path, User-Agent, IP) và quan sát khi nào thử thách biến mất.
Cách xác định quy tắc nào vừa kích hoạt
Khi CAPTCHA xuất hiện, hãy đọc kỹ phản hồi HTTP thay vì đoán. Hàm dưới đây kiểm tra mã trạng thái và nội dung trang để phân loại thử thách:
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
Ngoài ra, mọi phản hồi từ Cloudflare đều kèm header cf-ray. Nếu bạn là người vận hành trang đích, ID này tra được trong dashboard Cloudflare (Security > Events), cho biết chính xác quy tắc nào đã khớp. Nếu bạn chỉ test từ ngoài, cf-ray vẫn hữu ích để đính kèm khi báo lỗi cho đội vận hành.
Giải các thử thách do WAF kích hoạt
Python: chọn phương thức theo loại thử thách
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
Logic: managed_challenge gửi kèm sitekey thật lấy từ trang, còn js_challenge dùng sitekey cố định "managed". Task ID trả về từ in.php; polling res.php mỗi 5 giây tới khi status bằng 1.
Bản 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;
}
Bản Node.js làm cùng việc: chọn method/sitekey theo challengeType, gửi task tới in.php, polling res.php tối đa 60 lần trước khi báo timeout.
Khi quy tắc WAF thay đổi
Người vận hành trang không để yên quy tắc WAF mãi — họ chỉnh theo mùa bán hàng, sự cố bảo mật, hoặc siết chặt dần theo thời gian. Mỗi kiểu thay đổi ảnh hưởng khác nhau đến tự động hóa:
| Thay đổi | Ảnh hưởng đến tự động hóa | Cách phát hiện |
|---|---|---|
| Thêm quy tắc mới | Thử thách mới xuất hiện trên đường dẫn trước đây chạy trơn tru | Theo dõi thay đổi trạng thái 503/403 |
| Xóa quy tắc | Thử thách biến mất | 200 thay vì 503 như trước |
| Nâng cấp hành động (Managed → Block) | Thử thách giải được biến thành chặn cứng | 403 thay vì 503 |
| Nới lỏng hành động (Block → Managed) | Chặn cứng biến thành thử thách giải được | 503 kèm trang thử thách |
| Đổi ngưỡng (bot score 30 → 50) | Nhiều request bị thử thách hơn | Tần suất gặp thử thách tăng |
| Đổi phạm vi đường dẫn | Ảnh hưởng đến các URL khác | Đường dẫn mới bắt đầu trả về thử thách |
Theo dõi thay đổi tự động
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)
Script chạy nền, kiểm tra danh sách URL mỗi giờ (interval=3600) và chỉ in ra khi trạng thái thay đổi — cách rẻ để phát hiện sớm một trang vừa siết thêm quy tắc WAF, thay vì chỉ biết khi pipeline đã lỗi hàng loạt.
Câu hỏi thường gặp
CAPTCHA xuất hiện ngẫu nhiên trên một trang, nguyên nhân do đâu?
Gần như chắc chắn là một quy tắc WAF khớp với một số thuộc tính request cụ thể, không phải toàn bộ traffic — ví dụ bot score thấp hơn ngưỡng, IP nằm trong danh sách quốc gia bị thử thách, hoặc User-Agent đáng ngờ.
Managed Challenge khác gì so với JS Challenge?
Managed Challenge để Cloudflare tự chọn mức độ thử thách theo điểm rủi ro (vô hình, Turnstile, hoặc trang JS) — JS Challenge luôn cố định là trang JavaScript, không có nhánh vô hình. Cả hai đều trả 503, nhưng script cần logic khác nhau: turnstile cho Managed Challenge, cloudflare_challenge cho JS Challenge.
CaptchaAI có xử lý được Interactive Challenge (CAPTCHA kiểu cũ) không?
Có. Interactive Challenge trả về mã 403 thay vì 503, nhưng vẫn giải được — dùng cùng phương thức turnstile. Đây là hành động cũ Cloudflare không còn khuyến khích, nên bạn sẽ gặp nó ít hơn nhiều so với Managed Challenge.
Có xem trước được quy tắc WAF của một trang mục tiêu không?
Không. Quy tắc WAF là cấu hình riêng tư của người vận hành, không public ra ngoài. Bạn chỉ suy luận được từ hành vi quan sát — path nào bị thử thách, từ IP/quốc gia nào, và loại thử thách hiện ra.
Sau khi giải xong Managed Challenge, cookie phiên dùng lại được bao lâu?
Khoảng 30 phút. Cookie phát ra sau khi giải thành công gắn với IP và User-Agent tại thời điểm giải, nên request tiếp theo cùng phiên đi qua mà không bị thử thách lại — đổi IP hoặc User-Agent giữa chừng sẽ khiến cookie mất hiệu lực sớm hơn.
Tóm tắt
Quy tắc WAF của Cloudflare kích hoạt CAPTCHA dựa trên các điều kiện có thể cấu hình: bot score, quốc gia, đường dẫn, header hoặc tần suất request. Hành động phổ biến nhất là Managed Challenge, được Cloudflare hiển thị thích ứng dưới dạng vô hình, Turnstile, hoặc trang JS challenge. Giải các thử thách này bằng CaptchaAI với phương thức turnstile hoặc cloudflare_challenge tùy vào loại thử thách đang hiện ra. Riêng chặn cứng (403 từ hành động Block) thì không có gì để giải — việc cần làm là đổi mẫu request hoặc IP, không phải tìm solver.
Bài viết liên quan
Đọc thêm để hiểu sâu hơn về cơ chế Cloudflare Challenge: