📚 系列導覽:本篇是「Cloudflare 反爬蟲機制」系列第 4 篇(完結),對應總覽篇六層偵測中的第 5 層(瀏覽器執行層)。
# 筆記 主題 01 反爬蟲偵測總覽 六層偵測全景、請求時序地圖 02 TLS 指紋:JA3 與 JA4 ClientHello 為何洩漏身分、兩代演算法 03 HTTP/2 指紋與 Akamai Fingerprint 連線層參數指紋、三方交叉驗證 04 Managed Challenge 與 Bot Score(本篇) 主動挑戰機制、PAT、Cookie 綁定
目錄
- 什麼是 Managed Challenge?
- 基礎補課:挑戰機制的四代演進
- Managed Challenge 的決策流程
- 檢驗項目 1:非互動式工作量證明
- 檢驗項目 2:瀏覽器環境探測
- 檢驗項目 3:跨層一致性檢查
- Private Access Tokens:裝置級證明
- Cookie 的角色與綁定條件
- Bot Score 的組成與級距
- 為什麼真人幾乎無感
- 讀懂挑戰頁與回應標頭
- 對比分析:各種挑戰類型
- 常見問題
- 總結
什麼是 Managed Challenge?
Managed Challenge(受管理挑戰)是 Cloudflare 的一種防護動作:伺服器暫時不回傳網頁內容,而是先要求客戶端證明自己具備真實瀏覽器環境,通過後才放行。
「Managed」(受管理)這個字是重點——挑戰的具體形式不是固定的,而是由 Cloudflare 依當下的訊號動態決定。同一條規則,對不同請求可能產生完全不同的處理:
同一條「Managed Challenge」規則
├─ 對真人裝置 → 用 Private Access Token 靜默驗證,或直接放行
├─ 對可疑客戶端 → 發出非互動式 JavaScript 挑戰
└─ 對高風險客戶端 → 發出需要使用者互動的挑戰
這與前兩篇的被動指紋是不同性質的機制。指紋是觀察,挑戰是互動——它會主動要求客戶端做一件事,藉此檢驗指紋看不到的東西。
為什麼需要主動挑戰?
只有被動指紋的限制:
- 指紋只能證明「你用哪一套函式庫」,不能證明「你有沒有 JavaScript 引擎」
- 專門模仿指紋的工具存在,被動訊號可能全部對上
- 無法檢驗 GPU、字型、螢幕、時區這類真實裝置才有的特徵
主動挑戰能檢驗的東西:
- 是否具備完整的 JavaScript 執行環境
- 是否有真實的圖形堆疊(GPU 與驅動)
- 是否處於自動化控制之下
- 是否願意付出運算成本
核心特點
Managed Challenge = 動態選擇的挑戰形式
+ 非互動式工作量證明
+ 瀏覽器環境探測
+ 跨層一致性檢查
基礎補課:挑戰機制的四代演進
理解演進脈絡,才能理解為什麼現在的機制長這樣。
第一代:CAPTCHA(圖形驗證碼)
做法:顯示一組需要人類辨識的圖形或文字,要求使用者手動完成。
問題:
- 對真人的成本極高:中斷瀏覽流程,通常需要 10 至 30 秒
- 無障礙災難:視障使用者難以完成,音訊替代方案體驗更差
- 對機器的成本反而下降:影像辨識模型的進步讓自動求解越來越容易
- 商業損害明確:每一次 CAPTCHA 都會流失一部分真實客戶
演進到後期,成本結構完全反轉——對真人很痛,對機器不痛。Cloudflare 於 2021 年公開說明要淘汰 CAPTCHA,並在後續版本中完全移除這個選項。
第二代:JS Challenge / I'm Under Attack Mode
做法:回傳一個中介頁面,顯示「正在檢查你的瀏覽器」,執行一段 JavaScript,約五秒後自動跳轉回原頁面。
特徵:
- 狀態碼通常是
503 - 不需要使用者操作,但必然有五秒左右的可見延遲
- 只檢驗「能不能執行 JavaScript」,檢查深度有限
問題:五秒的固定延遲對正常瀏覽仍是明顯干擾。而且它把成本平均分攤給所有人,包括那些被動訊號早就顯示是真人的請求。
第三代:Managed Challenge(現行主流)
做法:先評估被動訊號,只在必要時才發出挑戰,且挑戰形式動態選擇。
關鍵改變:
- 大多數真人請求完全不會看到挑戰頁
- 需要挑戰時優先用非互動式,目標是百毫秒級完成
- 真實裝置可透過 Private Access Token 靜默通過
- 只有高風險請求才會遇到需要互動的形式
這一代的核心思想是把成本從「所有人平均分攤」改成「按風險分配」。
第四代:Turnstile(開放給網站自行嵌入)
做法:Cloudflare 把同一套偵測引擎包成一個可嵌入的網頁元件,網站可以放在表單裡(例如註冊、登入、留言)。
與 Managed Challenge 的關係:
| Managed Challenge | Turnstile | |
|---|---|---|
| 誰觸發 | Cloudflare 在邊緣自動插入 | 網站自己嵌入頁面中 |
| 位置 | 攔在整個頁面之前 | 表單裡的一個元件 |
| 偵測引擎 | 同一套 | 同一套 |
| 是否需要在 Cloudflare 後面 | 是 | 否,任何網站都能用 |
理解這層關係有助於認識:Turnstile 不是「另一種 CAPTCHA」,而是同一套環境探測邏輯的另一種投放方式。
四代對照
第一代 CAPTCHA 真人成本 極高 │ 機器成本 逐漸降低 → 已淘汰
第二代 JS Challenge 真人成本 中 │ 機器成本 中 → 舊機制
第三代 Managed 真人成本 近零 │ 機器成本 高 → 現行
第四代 Turnstile 真人成本 近零 │ 機器成本 高 → 網站自行嵌入
成本結構的變化就是整段演進史:目標始終是拉大「真人成本」與「機器成本」的差距。
Managed Challenge 的決策流程
這張流程圖是本篇最重要的一張圖。
請求進來
│
▼
① 採集被動訊號(零成本,總是執行)
IP/ASN、JA4、HTTP/2 指紋、標頭一致性、歷史行為
→ 計算 Bot Score
│
▼
② 已有有效的 __cf_bm cookie?
├─ 是 ──▶ 直接放行(此連線近期已評估過)
└─ 否
│
▼
③ 客戶端能提供 Private Access Token?
├─ 是 ──▶ 驗證裝置級證明 ──▶ 通過 ──▶ 放行(使用者完全無感)
└─ 否
│
▼
④ Bot Score 是否足夠高?
├─ 是 ──▶ 放行(連挑戰頁都不出現) ◀── 絕大多數真人走這條
└─ 否
│
▼
⑤ 發出非互動式挑戰
回傳挑戰頁 → 客戶端執行 JavaScript → 回傳結果
├─ 通過 ──▶ 發放 cf_clearance cookie ──▶ 轉回原頁面
└─ 失敗或超時
│
▼
⑥ 升級為互動式挑戰,或依規則直接封鎖
這張圖要讀出的三件事
1. 挑戰是最後手段,不是第一步
步驟 ① 到 ④ 都不需要客戶端配合。只有在被動訊號無法決定時,才進入步驟 ⑤。這解釋了為什麼「網站有防護」與「使用者沒感覺」可以同時成立。
2. 有三條「完全無感」的通道
步驟 ②(cookie 快取)、③(裝置證明)、④(分數足夠)都會直接放行。真人使用者幾乎總是命中其中一條。
3. 自動化程式為什麼幾乎必定進入步驟 ⑤ 或 ⑥
- 步驟 ②:沒有 cookie,因為第一次來
- 步驟 ③:拿不到 Token,因為需要硬體級裝置證明
- 步驟 ④:分數不足,因為 JA4 與 HTTP/2 指紋已經顯示不是瀏覽器
三條無感通道全部走不通,於是必然遇到挑戰。這不是針對性的封鎖,而是流程的自然結果。
檢驗項目 1:非互動式工作量證明
做法
挑戰頁載入一段 JavaScript,要求客戶端執行一定量的運算,並在時限內回傳結果。伺服器驗證答案是否正確。
「非互動式」意思是不需要使用者做任何事——沒有圖片要點、沒有文字要輸入,純粹是瀏覽器在背景算完。
為什麼有效:不對稱成本
這是整個設計最優雅的地方:
對單一真人使用者
一次挑戰 ≈ 幾十到數百毫秒的 CPU 時間
一天可能遇到幾次
→ 總成本:可忽略
對想抓一百萬個頁面的自動化程式
一百萬次挑戰 × 每次數百毫秒 CPU
→ 總成本:數十到數百 CPU 小時
→ 加上必須執行 JavaScript,記憶體與程序成本同步上升
**同一個機制,對兩種使用者產生數個量級的成本差異。**這正是工作量證明類機制的核心價值:它不試圖「判斷」誰是機器人,而是讓大規模自動化在經濟上不划算。
附帶效果
工作量證明還順便檢驗了一件事:客戶端是否真的有 JavaScript 引擎。
一個只發 HTTP 請求、不執行 JavaScript 的程式,連題目都無法計算。這一步就篩掉了絕大多數簡易爬蟲——它們不是算錯,而是完全沒有回應。
檢驗項目 2:瀏覽器環境探測
這是挑戰腳本真正做深度檢查的部分。目標是驗證「這是不是一個真實的、非自動化控制的瀏覽器」。
探測項目一覽
| 類別 | 檢查對象 | 為什麼有辨識力 |
|---|---|---|
| 自動化標記 | navigator.webdriver |
WebDriver 標準要求自動化控制時此值為 true |
| 自動化標記 | 除錯協定的副作用 | 某些自動化控制方式會留下可觀測的痕跡 |
| 圖形堆疊 | Canvas 指紋 | 繪製文字/圖形後取像素雜湊,結果受 GPU、驅動、字型渲染影響 |
| 圖形堆疊 | WebGL 資訊 | 可取得 GPU 廠商與型號字串;軟體渲染與真實 GPU 差異明顯 |
| 瀏覽器 API | window.chrome 等物件 |
特定瀏覽器獨有的物件,其存在與內部結構都可檢查 |
| 瀏覽器 API | navigator.plugins、languages、hardwareConcurrency、deviceMemory |
真實裝置有合理的值;自動化環境常是空的或不合理 |
| 時間特徵 | performance.now() 解析度 |
不同環境的計時器精度不同 |
| 時間特徵 | JavaScript 執行耗時 | 執行速度是否符合宣稱的硬體等級 |
| 環境資訊 | 字型清單、螢幕解析度、時區 | 真實裝置的組合有統計規律 |
| 無頭瀏覽器特徵 | 缺失的 API、渲染差異 | 無頭模式與有頭模式存在可觀測差異(新版已大幅收斂) |
一個特別優雅的檢查:偽造本身可被偵測
假設有人想隱藏自動化痕跡,於是覆寫了那個標記屬性,讓它回報 false。
問題是:覆寫這個動作本身會留下痕跡。
JavaScript 中,原生函式(由瀏覽器引擎以 C++ 實作)與使用者定義的函式,在被要求輸出自身原始碼時表現不同:
原生函式 → 輸出類似「function () { [native code] }」
被覆寫的 → 輸出實際的 JavaScript 原始碼,或其他可辨識的形式
所以檢查腳本不只讀屬性的值,還可以檢查「這個屬性的取值器是不是原生實作」。
這帶出一個很重要的一般性觀念:
隱藏一個特徵,需要修改環境;而修改環境本身也是一個特徵。
這使得環境偽造變成一場層層遞進的競賽——每加一層隱藏,就多一個可被偵測的修改痕跡。這是主動挑戰在架構上的優勢:防禦方可以任意增加檢查項目,而每個新項目都需要偽造方額外處理。
Canvas 與 WebGL 為什麼難偽造
這兩項檢查的本質是要求客戶端證明自己有真實的圖形硬體。
Canvas 指紋
請客戶端畫一段特定的文字與圖形
→ 取得像素資料的雜湊
→ 結果受 GPU 型號、驅動版本、字型檔案、反鋸齒設定影響
→ 同一段程式在不同裝置上產生不同結果
WebGL 資訊
可查詢 GPU 的廠商與渲染器名稱字串
→ 真實 GPU 有具體型號
→ 純軟體渲染會顯示軟體渲染器的名稱
→ 在無 GPU 的伺服器環境中,這一點很難掩蓋
要偽造,必須提供一組在統計上合理的值——不只是隨便填一個字串,還要與宣稱的作業系統、瀏覽器版本、螢幕解析度互相吻合。
檢驗項目 3:跨層一致性檢查
挑戰腳本收集到的環境資訊,會與 HTTP 層、TLS 層的宣稱做比對。這是系列第 03 篇三方交叉驗證的延伸——現在變成四方。
TLS 層(JA4) 說:某個 TLS 實作
HTTP/2 層(Akamai) 說:某個 HTTP/2 實作
HTTP 層(UA、Client Hints)說:某個瀏覽器 + 某個作業系統
JavaScript 層 說:某組 GPU、字型、螢幕、時區
↓ 四方比對
任何一組矛盾都是訊號
典型的矛盾樣態
| 矛盾 | 說明 |
|---|---|
| Client Hints 說是 A 作業系統,WebGL 顯示 B 作業系統的驅動 | 環境偽造不完整 |
| 宣稱是行動裝置,但螢幕解析度與觸控 API 不符 | 常見於簡易偽裝 |
| 時區與 IP 的地理位置差距過大 | 可能透過代理,也可能是偽造 |
| 宣稱高階硬體,但 JavaScript 執行速度異常慢 | 執行環境與宣稱不符 |
| JA4 顯示某函式庫,但 JavaScript 環境完整 | 不常見的組合,值得注意 |
為什麼一致性比單項更難處理
要通過單項檢查,只需要提供一個合理的值。要通過一致性檢查,必須提供一整組互相吻合的值,而且這組值還要與更底層(TLS、HTTP/2)的指紋吻合。
真實裝置天生一致——因為它的 GPU、作業系統、瀏覽器、TLS 函式庫本來就是同一台機器上的真實元件。這個不對稱貫穿整個系列。
Private Access Tokens:裝置級證明
這是現代機制中最值得理解的一塊,因為它是「真人完全無感」的主要原因,而且它的隱私設計相當精巧。
要解決的問題
傳統挑戰的困境:
要確認「這是真人」→ 就要收集裝置資訊 → 但這侵犯隱私
要保護隱私 → 就不能收集資訊 → 但無法確認真人
Private Access Tokens(PAT)用密碼學的方式繞過這個兩難。它建立在 IETF 的 Privacy Pass 架構之上。
四個角色
① Client(客戶端) 使用者的裝置與瀏覽器
② Origin(網站) 需要確認「這是真裝置」的一方
③ Attester(證明方) 作業系統廠商,能驗證裝置真偽
④ Issuer(發行方) 簽發 token 的一方(此情境下是 Cloudflare)
流程
① Origin 需要驗證
│ 「請給我一個 token」
▼
② Client 向 Attester 請求
│ 用裝置的硬體金鑰證明:
│ 「我是一台真實的、未被篡改的裝置,
│ 且使用者已登入其帳號」
▼
③ Attester 驗證裝置,轉交給 Issuer
│
▼
④ Issuer 用「盲簽章」簽發 token
│ ← 關鍵:Issuer 簽了名,但看不到自己簽了什麼
▼
⑤ Client 把 token 交給 Origin
│
▼
⑥ Origin 驗證簽章有效 → 確認「這是真裝置」→ 放行
隱私設計:三方各自知道的資訊被刻意切開
這是整個設計的核心。盲簽章(blind signature)讓簽署方能對一份資料簽名,卻無法看到資料內容。結果是:
| 角色 | 知道什麼 | 不知道什麼 |
|---|---|---|
| Attester(作業系統廠商) | 這是哪一台裝置 | 使用者在訪問哪個網站 |
| Issuer(Cloudflare) | 有人請求了 token | 是哪一台裝置、要訪問哪個網站 |
| Origin(網站) | 這是一台真實裝置 | 是哪一台裝置、是誰 |
沒有任何單一方能同時知道「誰」與「在看什麼」。 這是一個真正的隱私保護設計,不是行銷說法——它由密碼學保證,不依賴任何一方的承諾。
效果與限制
對真人的效果:
- 支援的裝置與瀏覽器上,驗證在毫秒級完成
- 不執行任何 JavaScript 挑戰、不顯示任何頁面
- 使用者完全不知道發生了驗證
為什麼自動化程式拿不到:
因為第 ② 步需要硬體級的裝置證明——由裝置安全晶片中的金鑰簽署,而該金鑰無法被匯出或模擬。一個跑在雲端虛擬機上的程式無法提供這種證明。
限制:
- 需要作業系統與瀏覽器支援(目前主要是較新版本的 Apple 平台 Safari,以及 Google 在 Android/Chrome 上的等價機制)
- 不支援的環境會退回到一般的挑戰流程
為什麼這個機制值得理解
它示範了一個重要的觀念轉變:
從「收集足夠資訊來判斷你是誰」,轉向「用密碼學證明一個屬性,而不揭露身分」。
這與零知識證明的思路相通,也是隱私保護技術的一般方向。
Cookie 的角色與綁定條件
Cloudflare 會設定幾個 cookie,各有明確分工。理解它們的綁定條件,能解釋一個很常見的困惑。
主要的 cookie
| Cookie | 用途 | 典型存活時間 | 綁定條件 |
|---|---|---|---|
__cf_bm |
Bot Management。標記「此連線近期已被評估」,避免重複評估 | 約 30 分鐘 | IP + User-Agent(及指紋特徵) |
cf_clearance |
挑戰通過憑證。持有它可跳過挑戰 | 由網站設定(數十分鐘到數小時) | IP + User-Agent + TLS/HTTP2 指紋 |
__cfruid |
速率限制相關 | 連線期間 | — |
__cflb |
負載平衡的連線黏著(與 bot 偵測無關) | 依設定 | — |
為什麼「複製瀏覽器的 cookie 到程式裡」會失敗
這是很常見的嘗試,也是很常見的挫折。原因在於綁定:
在瀏覽器中通過挑戰
→ Cloudflare 發放 cf_clearance
→ 同時記錄:發放給哪個 IP、哪個 UA、哪組指紋
把 cookie 複製到程式中使用
→ Cloudflare 驗證 cookie 時重新檢查這些條件
→ IP 或許相同、UA 可以照抄
→ 但 TLS 指紋與 HTTP/2 指紋對不上(前兩篇的內容)
→ cookie 判定無效
關鍵理解:這不是 bug,也不是額外的防護層,而是 cookie 設計上的固有屬性。cf_clearance 證明的是「這個特定的客戶端環境通過了挑戰」,而不是「這個字串可以換內容」。
換個角度看:如果 cookie 可以自由轉移,那麼通過一次挑戰就能大量複製使用,整個機制就失去意義了。綁定是它有效的前提。
__cf_bm 的角色常被誤解
__cf_bm 不是用來追蹤個別使用者的。它的作用是快取一個判斷結果:
第一次請求:完整評估所有訊號(有計算成本)
→ 判定為真人 → 設定 __cf_bm
後續請求(30 分鐘內):看到有效的 __cf_bm → 跳過完整評估
這是效能優化,也是「為什麼真人只有第一次可能感覺到延遲、之後完全順暢」的原因之一。
Bot Score 的組成與級距
級距
| 分數 | 定義 | 含義 |
|---|---|---|
| 1 | Automated | 確定是自動化程式 |
| 2–29 | Likely automated | 很可能是自動化程式 |
| 30–99 | Likely human | 很可能是真人 |
| 0 或無值 | — | 未計算(方案未啟用或不適用) |
輸入訊號
分數由多個來源綜合而成,涵蓋本系列所有篇章的內容:
Bot Score
├─ 網路層 IP 信譽、ASN 類型、TCP 指紋 ← 系列 01
├─ TLS 層 JA3 / JA4 指紋 ← 系列 02
├─ HTTP/2 層 Akamai 指紋 ← 系列 03
├─ HTTP 層 標頭一致性、Client Hints 完整性 ← 系列 01
├─ 執行層 挑戰結果、環境探測結果 ← 本篇
├─ 行為層 請求節奏、資源載入模式 ← 系列 01
└─ 全網統計 相同指紋在其他網站的歷史行為
最後一項值得注意:因為 Cloudflare 同時服務大量網站,它能看到「同一個指紋在其他地方做了什麼」。這是單一網站自建防護無法取得的視角。
WAF 可用的欄位
網站管理者可在自訂規則中使用這些欄位:
| 欄位 | 內容 |
|---|---|
cf.bot_management.score |
綜合 Bot Score |
cf.bot_management.verified_bot |
是否為已驗證的良性爬蟲 |
cf.bot_management.ja3_hash |
JA3 指紋 |
cf.bot_management.ja4 |
JA4 指紋 |
cf.bot_management.detection_ids |
觸發的偵測規則編號 |
cf.bot_management.static_resource |
是否為靜態資源請求 |
三個容易誤解的地方
1. 分數是欄位,不是行為
Cloudflare 計算分數,但要不要擋、怎麼擋,完全由各網站自己的規則決定。同樣分數為 3 的請求,在 A 網站被放行、在 B 網站被封鎖,是完全正常的。
2. 分數不是機率
50 分不代表「50% 機率是機器人」。它是一個經過校準的信心指標,級距的分界(1、29、30)比具體數值更有意義。
3. 有一整類「分數低但被歡迎」的流量
Verified Bot 是一個獨立的標記,用於已驗證的良性爬蟲(搜尋引擎、監控服務等)。驗證方式通常是反向 DNS 查詢加上官方公布的 IP 範圍比對。
這些爬蟲的 Bot Score 當然很低——它們確實是機器人——但規則上通常會放行。
這帶出整個系列最實務的一個結論:
Bot 偵測的目的不是「擋掉所有機器人」,而是「區分被授權的機器人與未經授權的機器人」。
因此,取得授權(申請 API、加入白名單、取得資料授權)在技術上永遠是最穩定的路徑——它讓問題從「如何通過偵測」變成「不需要通過偵測」。這不只是合規考量,也是工程上成本最低的解。
為什麼真人幾乎無感
這是使用者最直觀的疑問,答案在於一組刻意的工程取捨。
原因 1:商業誘因
Cloudflare 的客戶是網站經營者,他們最在意的指標是轉換率。每一個多餘的挑戰頁都是流失的客戶。
因此產品設計上有一個硬約束:寧可放過一些低風險的自動化流量,也不要擋到真實使用者。這個約束直接決定了決策流程的形狀——為什麼要有三條無感通道、為什麼挑戰是最後手段。
原因 2:判斷成本被前移
被動訊號(IP、JA4、HTTP/2 指紋)在連線建立階段就採集完成,零額外往返、零額外延遲。
傳統思路:先讓請求進來,再檢查內容,發現可疑就攔下
→ 檢查發生在應用層,需要額外往返
現代思路:在連線握手階段就完成大部分判斷
→ 資訊本來就要傳,採集不增加任何成本
大多數請求在步驟 ① 到 ④ 就決定完了,根本不進入挑戰流程。
原因 3:挑戰本身的延遲預算
即使需要挑戰,非互動式挑戰的目標是百毫秒級完成。使用者感受到的通常只是頁面「閃一下」,而不是明顯的等待。
這與第二代 JS Challenge 的固定五秒是本質差異。
原因 4:結果會被快取
__cf_bm 讓判斷結果在約 30 分鐘內重複使用。即使第一個請求經過完整評估,後續的瀏覽都直接放行。
一個重要的認知修正
❌ 「使用者無感」→ 推論「網站沒有防護」或「防護很弱」
✅ 「使用者無感」→ 推論「判斷成本被前移到連線階段,
且成本按風險分配」
無感是精準的結果,不是薄弱的證據。 一個會讓所有人都看到挑戰頁的系統,其實是判斷能力不足——它無法區分風險,只能一律收費。
讀懂挑戰頁與回應標頭
當請求被挑戰攔下時,回應本身包含不少可解讀的資訊。以下為去識別化的代表性樣本。
回應標頭
| 標頭 | 意義 |
|---|---|
server: cloudflare |
流量經過 Cloudflare |
cf-ray: <id>-<機房代碼> |
請求追蹤 ID,結尾三字母是處理該請求的資料中心 |
cf-mitigated: challenge |
這個請求被挑戰攔下(最有價值的單一線索) |
server-timing: chlray;desc="<id>" |
本次挑戰的追蹤 ID |
accept-ch: <清單> |
向瀏覽器索取 Client Hints |
critical-ch: <清單> |
要求瀏覽器立即重送請求並附上這些提示 |
cross-origin-embedder-policy: require-corp |
防止挑戰頁被外部頁面以 iframe 包裝 |
cross-origin-opener-policy: same-origin |
隔離挑戰頁的瀏覽環境 |
content-security-policy: ... |
嚴格限制挑戰頁可載入的資源來源 |
從標頭能判斷什麼
判斷擋你的是哪一種機制:
403 + cf-mitigated: challenge → 主動挑戰,理論上可通過
403 + 無 cf-mitigated → WAF 規則直接封鎖,性質不同
503 + 舊式挑戰腳本 → 第二代 JS Challenge
429 → 速率限制,與 bot 判定無關
判斷指紋收集的積極程度:
accept-ch 的清單長度是一個訊號。基本的 Client Hints 只有瀏覽器品牌與平台;若清單延伸到 CPU 架構、位元數、裝置型號、完整版本清單,代表有人刻意把裝置資訊的熵值拉到最高——這是經過調整的設定,而非預設值。
搭配 critical-ch 就更明確:它不接受先給一次不完整的請求。
判斷挑戰頁的防護強度:
cross-origin-embedder-policy 與 cross-origin-opener-policy 的存在,是為了封住「用 iframe 包裝挑戰頁、從外部竊取通過憑證」這條路。看到這些標頭,代表挑戰頁本身也做了隔離加固。
挑戰頁內嵌的設定物件
挑戰頁的 HTML 會內嵌一個設定物件(常見名稱為 window._cf_chl_opt),描述本次挑戰的參數。常見欄位:
| 欄位 | 意義 |
|---|---|
cType |
挑戰類型:managed / interactive / non-interactive |
cRay |
本次挑戰的追蹤 ID,對應 Cloudflare 日誌 |
cZone |
目標網域 |
cITimeS |
挑戰發出的 Unix 時間戳 |
cN |
內容安全政策使用的 nonce |
cTplV |
挑戰頁模板版本 |
cH、md 等 |
挑戰的加密參數與挑戰資料,供伺服器端驗證 |
cType 是其中最有資訊量的一個:它直接說明對方開啟的是哪一級的挑戰。managed 表示需要執行 JavaScript 的完整挑戰,non-interactive 是較輕量的形式。
另外,挑戰頁通常包含一個自動重新整理的設定,讓頁面在停留過久時自我更新——因為挑戰參數有時效性,過期的挑戰無法通過驗證。
對比分析:各種挑戰類型
| 特性 | CAPTCHA | JS Challenge | Managed Challenge | Turnstile |
|---|---|---|---|---|
| 世代 | 第一代 | 第二代 | 第三代 | 第四代 |
| 狀態 | 已淘汰 | 舊機制 | 現行主流 | 現行(自行嵌入) |
| 需要使用者操作 | ✅ 總是 | ❌ | ❌ 通常不需要 | ❌ 通常不需要 |
| 真人可見延遲 | 10–30 秒 | 約 5 秒 | 通常無感 | 通常無感 |
| 典型狀態碼 | 403 |
503 |
403 |
由網站處理 |
| 檢查深度 | 僅「是不是人」 | 能否執行 JS | 完整環境探測 | 完整環境探測 |
| 支援裝置級證明 | ❌ | ❌ | ✅ PAT | ✅ PAT |
| 無障礙友善 | ❌ 嚴重問題 | 🟡 尚可 | ✅ 良好 | ✅ 良好 |
| 觸發位置 | 頁面之前 | 頁面之前 | 頁面之前 | 表單元件內 |
| 需在 Cloudflare 後 | 是 | 是 | 是 | ❌ 任何網站可用 |
何時會遇到哪一種?
遇到 Managed Challenge(多數情況)
→ 網站啟用了 Bot Management 或 WAF 規則
→ 你的請求分數不足以直接放行
遇到需要互動的挑戰
→ 分數非常低,或非互動式挑戰失敗
→ 也可能是網站刻意選擇較嚴格的動作
遇到 Turnstile
→ 網站自己在表單裡嵌入的,與邊緣防護是不同層
完全沒遇到挑戰
→ 可能命中了三條無感通道之一
→ 也可能網站根本沒啟用相關規則
常見問題
問題 1:Managed Challenge 就是 CAPTCHA 嗎?
回答:不是,這是最常見的誤解。
CAPTCHA 是第一代機制,必然需要使用者手動操作。Managed Challenge 是第三代,多數情況完全不需要使用者參與——它優先嘗試裝置級證明與非互動式挑戰,只在風險很高時才升級到需要互動的形式。
Cloudflare 淘汰 CAPTCHA 的原因正是它的成本結構已經反轉:對真人越來越痛,對機器越來越不痛。
問題 2:為什麼會卡在「無限挑戰迴圈」?
回答:挑戰通過後需要儲存並回傳 cf_clearance,任何打斷這個循環的因素都會造成迴圈。常見原因:
| 原因 | 說明 |
|---|---|
| Cookie 被封鎖 | 瀏覽器設定或擴充功能阻止設定 cookie,憑證無法保存 |
| IP 在請求之間變動 | 某些 VPN 或行動網路會切換出口 IP,導致憑證的綁定條件不符 |
| 系統時鐘偏差過大 | 挑戰參數有時效性,時鐘不準會導致驗證失敗 |
| 挑戰腳本被阻擋 | 隱私擴充功能或內容封鎖器攔下了挑戰腳本 |
| 第三方 cookie 政策 | 特定的隔離設定可能影響憑證的儲存 |
排查順序:先確認 cookie 能正常設定,再檢查系統時間,然後暫時停用瀏覽器擴充功能測試。
問題 3:用無頭瀏覽器一定會被抓到嗎?
回答:不一定,但預設設定下容易被抓到。
真實瀏覽器引擎解決了很多問題——它有完整的 JavaScript 引擎、真實的瀏覽器 API、能執行挑戰腳本。這相對於純 HTTP 客戶端是巨大的進展,也讓 TLS 與 HTTP/2 指紋自然吻合。
但仍有幾個獨立的問題:
- 自動化控制本身有標記:WebDriver 標準要求自動化時
navigator.webdriver為true - 隱藏標記會留下新痕跡:如前所述,覆寫屬性本身可被偵測
- 圖形環境:伺服器環境常缺少真實 GPU,Canvas 與 WebGL 的結果與桌機不同
- 其他層獨立運作:機房 IP(第 1 層)與行為模式(第 6 層)不會因為換瀏覽器而改變
所以「用無頭瀏覽器」解決的是第 5 層,不是全部六層。
問題 4:Cloudflare 能藉這些機制識別出「我是誰」嗎?
回答:不能,而且部分機制是刻意設計成不能的。
- TLS 與 HTTP/2 指紋是群組識別碼:同一版瀏覽器、同一作業系統的所有使用者共享同一個指紋。用它識別個人在原理上就不可行
__cf_bm是判斷結果的快取,不是使用者識別碼- Private Access Tokens 用盲簽章刻意切斷關聯:如前所述,沒有任何單一方能同時知道「誰」與「在看什麼」
環境探測(Canvas、WebGL、字型清單)確實產生較高的熵值,理論上有較強的識別潛力——這也是為什麼瀏覽器廠商持續在限制這些 API 的精度。但在 bot 偵測的用途上,這些資訊是用來判斷「這是不是真實裝置環境」,而非建立個人檔案。
問題 5:針對 AI 爬蟲的控制是同一套機制嗎?
回答:不完全相同,那是另一個機制層。
近年除了 Bot Score 之外,還發展出針對 AI 訓練與檢索爬蟲的獨立控制手段——包含集中管理 robots.txt 的宣告、針對已知 AI 爬蟲的分類與封鎖選項,以及讓網站對爬取行為收費的機制。
這些與本篇討論的偵測機制的差別在於:
Bot Score 機制 判斷「這是不是自動化程式」
AI 爬蟲控制機制 判斷「這個已知的爬蟲,網站允不允許 / 收多少費」
後者處理的是授權與商業條件,而非技術偽裝。這也再次呼應前面的結論:整個生態的發展方向是把「機器人存取」制度化,而不是無止境的技術對抗。
問題 6:常見誤解整理
誤解 1:看到 403 就是 IP 被封鎖了。
正確理解:403 加上 cf-mitigated: challenge 表示被挑戰攔下,IP 沒事,換 IP 也沒用。真正的 IP 層封鎖通常表現為連線失敗或特定的錯誤碼頁面。先看標頭再診斷。
誤解 2:拿到 cf_clearance 就能一直重複使用。
正確理解:它綁定 IP、User-Agent 與指紋特徵,而且會過期。設計上就不可轉移——否則通過一次就能無限複製,機制便失去意義。
誤解 3:挑戰頁沒出現,代表網站沒有防護。
正確理解:挑戰頁不出現是「被動訊號已足夠判定」的正常結果。三條無感通道任一條命中都會直接放行。
誤解 4:Turnstile 是 Cloudflare 版的 CAPTCHA。
正確理解:兩者的偵測邏輯完全不同。Turnstile 用的是與 Managed Challenge 相同的環境探測引擎,多數情況不需要使用者操作。它與 CAPTCHA 的相似點只有「都是嵌在表單裡的元件」這個外形。
誤解 5:非互動式挑戰只是形式,沒有真正的防護作用。
正確理解:它同時達成兩件事——篩掉所有不執行 JavaScript 的客戶端(這是絕大多數簡易自動化程式),以及對大規模抓取施加實質的運算成本。它的價值不在於「判斷得多準」,而在於改變經濟結構。
總結
核心要點
Managed Challenge = 動態選擇形式的主動挑戰
決策流程(挑戰是最後手段):
① 採集被動訊號 → 算 Bot Score
② __cf_bm 有效? → 放行 ┐
③ 有 PAT? → 放行 ├─ 三條無感通道
④ 分數足夠? → 放行 ┘
⑤ 發非互動式挑戰
⑥ 升級互動式 / 封鎖
挑戰檢驗三項:
├─ 非互動式工作量證明 → 不對稱成本 + 篩掉無 JS 客戶端
├─ 瀏覽器環境探測 → 自動化標記、Canvas/WebGL、API 完整性
└─ 跨層一致性檢查 → 四方比對(TLS / HTTP2 / HTTP / JS)
Bot Score:1 = 自動化,2–29 = 可能自動化,30–99 = 可能真人
Verified Bot 是獨立標記
理解重點
-
挑戰是最後手段:三條無感通道(cookie 快取、裝置證明、分數足夠)讓大多數真人根本不進入挑戰流程。這是「無感」的結構性原因。
-
不對稱成本是核心設計:非互動式挑戰對單一真人是幾十毫秒,對百萬次抓取是數十 CPU 小時。它不試圖判斷誰是機器人,而是讓大規模自動化在經濟上不划算。
-
隱藏特徵本身是特徵:覆寫自動化標記的動作可被偵測。這使環境偽造成為層層遞進的競賽,而防禦方可以任意增加檢查項目。
-
PAT 示範了新的思路:用密碼學證明「這是真裝置」這個屬性,而不揭露身分。盲簽章讓三方各自知道的資訊被刻意切開,沒有單一方能同時知道「誰」與「在看什麼」。
-
Cookie 的綁定是機制有效的前提:
cf_clearance綁定 IP、UA 與指紋,不是 bug 而是設計。可自由轉移的憑證等於沒有憑證。 -
無感不等於無防護:一個讓所有人都看到挑戰頁的系統,其實是判斷能力不足——它無法區分風險,只能一律收費。精準的系統才能做到只對可疑流量收費。
-
真正的分野是授權,不是技術:Verified Bot 的存在說明目標不是擋掉所有機器人,而是區分授權與未授權。取得授權讓問題從「如何通過偵測」變成「不需要通過偵測」——這在工程上也是成本最低的解。
快速記憶
| 方面 | 說明 |
|---|---|
| 是什麼 | 動態選擇形式的主動挑戰機制 |
| 「Managed」的含義 | 挑戰形式由 Cloudflare 依訊號動態決定,非固定 |
| 為什麼真人無感 | 三條無感通道 + 判斷成本前移 + 百毫秒延遲預算 + 結果快取 |
| 檢驗核心 | 是否有真實瀏覽器環境、是否受自動化控制、四層是否一致 |
| 最優雅的設計 | 不對稱成本 + PAT 的盲簽章隱私切分 |
| 最有價值的診斷線索 | cf-mitigated 標頭與挑戰設定物件的 cType |
| 最大的誤解 | 以為它等於 CAPTCHA |
學習路徑(系列完結)
1. 建立六層全景與請求時序地圖 ✓ 系列 01
2. 理解 TLS 層的第一份證據 ✓ 系列 02
3. 理解 HTTP/2 層的第二份證據 ✓ 系列 03
4. 理解主動挑戰在檢驗什麼 ✓ 本篇
貫穿全系列的一條主線:
真實瀏覽器的各層天生一致,不需努力;
模仿方需要讓多個獨立元件同時對應同一目標,且要持續追版本。
整套偵測體系建立在這個架構性的成本差異上,
而不是任何單一的聰明演算法。
延伸閱讀
- 反爬蟲偵測總覽 - 六層偵測全景與請求時序
- TLS 指紋:JA3 與 JA4 - 第一份證據
- HTTP/2 指紋與 Akamai Fingerprint - 第二份獨立證據
- Cloudflare 開發者平台總覽 - Cloudflare 產品地圖
- 認證與授權完全指南 - 憑證、token 與授權模型
- 雜湊與加密完全指南 - 簽章與雜湊基礎
- OWASP Top 10 - 常見網站安全風險
建立日期:2026-09-07 最後更新:2026-09-07