8.3 安全防護:WAF、Rate Limiting、Turnstile#
本節目標#
- 用 OWASP Top 10 快速掃描常見攻擊面,並對應到 Cloudflare 的防禦層。
- 設定 WAF:Managed Rules、自訂 expression、跳轉挑戰(Managed Challenge)。
- 用 Rate Limiting rule 抵擋暴力與資源濫用。
- 理解 Turnstile 為什麼是 CAPTCHA 的現代替代品,以及 bot 分數怎麼讀。
OWASP Top 10 精華回顧(網站架構視角)#
| # | 類別 | 典型例子 | Cloudflare 對應層 |
|---|---|---|---|
| A01 | Broken Access Control | 亂改 URL 存取他人資料 | 應用程式邏輯(你自己) |
| A02 | Cryptographic Failures | 沒有 HTTPS、弱雜湊 | Universal SSL(平台) |
| A03 | Injection | SQLi、XSS | WAF Managed Rules |
| A04 | Insecure Design | 可被無限呼叫的昂貴 API | Rate Limiting |
| A07 | Ident/Auth Failures | 弱密碼、暴力登入 | Turnstile + Rate Limiting |
| A10 | SSRF | 伺服器替攻擊者打內網 | WAF 表達式(擋 worker 內 fetch 目標) |
注意:Cloudflare 不是「全防」。A01 與業務邏輯漏洞只能由你自己的程式防,平台的價值是擋下「沒有業務意義的海量攻擊流量」。
WAF:兩種規則心法#
1. Managed Rules(買保險)#
SQLi、XSS、Log4Shell 這類已知攻擊特徵不值得自己寫,用 Cloudflare 維護的全域規則集即可(OWASP Core Ruleset、Cloudflare Managed Ruleset、WordPress 專集等)。重點在調靈敏度:
- 預設設定:Low 起步,避免 recall 太高導致誤殺(false positive 比 false negative 更常見於新站)
- 例外處理(Exception):發現某規則誤殺某路徑(如 RESTful API 用了奇怪的 header),針對該規則設 skip,而不是關掉整組
2. 自訂 expression(直覺式 firewall policy)#
Firewall Rules / WAF Custom Rules 的語法叫 wirefilter expression,像過濾器語言:
(http.host eq "api.example.com" and matches(http.request.uri.path, "^/admin"))
(http.user_agent contains "sqlmap" or cf.threat_score gt 20)
(not cf.client.bot and ip.src country in {"KP" "SY"})
動作可選:Block / JS Challenge / Managed Challenge(跳轉挑戰)/ Log / Skip(放行並結束後續規則)。
跳轉挑戰(Managed Challenge)的設計哲學#
請求 ─→ 規則命中 ─→ 不是直接 Block,而是丟出一個「看不見的挑戰頁」
├─ 正常瀏覽器:JS 環境指紋自然通過 → 轉發到源站
└─ 腳本/惡意爬蟲:環境不符合 → 卡在挑戰頁
這就是「挑戰」與「封鎖」的經濟學差異:Block 對誤判的代價是無限大(真人進不來),Managed Challenge 把誤判代價降為「多點一次」,把阻塞變成篩選。實務上:可確定的惡性 IP 段才用 Block,難以分辨的灰色語意(削價爬蟲、密碼嘗試)一律 challenge。
Rate Limiting:把 A04 變成平台責任#
Rate Limiting rule(Dashboard → Security → WAF → Rate limiting rules)邏輯是滑動窗口累積:
If: (http.request.uri.path eq "/api/login" and http.request.method eq "POST")
Accumulate requests for: 10 seconds, grouped by: cf.connecting_ip (取 top 100 維度)
Then: Block for 5 minutes
經典配比:
| 情境 | 規則 |
|---|---|
| 登入暴力破解 | 10 秒內同 IP > 3 次 POST /login → block 5 分鐘 |
| 貴 API(LLM 轉發) | 1 分鐘內同 IP > 10 次 → challenge |
| 全站 | 1 分鐘 > 300 request → challenge(別太緊,會誤殺 corporate NAT 的公司) |
注意分組維度 cf.connecting_ip(夠精準又可用);帳號維度需要應用層自己做(體驗:從你的 Worker 讀 turnstile 詳細結果 + D1 表做第二道閘門)。
Turnstile:CAPTCHA 的現代替代#
傳統 CAPTCHA(識字、選紅綠燈)有三宗罪:打斷人類 UX、被機器學習模型逐漸破解、注意力經濟(reCAPTCHA 被質疑提供免費標註勞動)。
Turnstile 改走被動驗證:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="0x4AAA..." data-theme="light"></div>
</form>
運作流程:
- 頁面載入時,
cf-turnstilewidget 在辨識與人機互動過程(滑鼠移動、JS 環境完整性、時間行為、WAF 累積的 IP 信譽等)收集訊號。 - 多數情況使用者不需點任何東西,幾秒內自動產生 token。
- 表單送出時帶
cf-turnstile-response,你的 Worker 用 siteverify API 驗證:
const siteverify = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
secret: env.TURNSTILE_SECRET,
response: form.get("cf-turnstile-response"),
remoteip: req.cf?.clientIp,
}),
}
).then(r => r.json());
if (!siteverify.success) return new Response("Bot?", { status: 403 });
// 若需要更細的判斷:siteverify.cdata / metadata(互動與分數等視指標)
理論核心:驗證「人」不可靠,驗證「環境」可靠。真實瀏覽器 + 真實使用者行為的信號集合很難在雜湊腳本中偽造(瀏覽器指紋 + PoP 看到的數以十億請求建立的信譽模型)。
Bot Fight Mode:免費的最後一道網#
前述 WAF 與 Rate Limiting 都需要你動手寫規則;Bot Fight Mode(Dashboard → Security → Bots)則是零設定的自動防線,免費方案即可開啟:
- Cloudflare 用全網信譽模型(每天數千億請求的行為資料)自動辨識明顯的惡意 bot,直接攔下,不經你的規則。
- 另有「確定為 bot 的請求」可透過
cf.client.bot欄位在自訂規則中引用(例如:允許 Googlebot、擋其他所有 bot)。 - 免費版的限制:沒有白名單。若它誤殺了你的監控爬蟲或自家測試腳本,只能關閉整個功能或用 Super Bot Fight Mode(付費)做細部豁免。
定位心法:Bot Fight Mode 是「保底」不是「精準打擊」。精密的業務防護仍應回到自訂 expression + Turnstile 三層組合:
第 0 層 Bot Fight Mode → 自動擋明顯惡意流量(零維運)
第 1 層 WAF Custom Rules → 業務語意(路徑、國家、UA)
第 2 層 Rate Limiting → 頻率濫用
第 3 層 Turnstile → 關鍵動作(註冊、登入、下單)的人機確認
小結與練習#
重點回顧
- 知名攻擊(SQLi、XSS、SSRF 特徵)交給 Managed Rules;業務語意用自訂 expression;不可分辨的灰區挑戰,不要 Block。
- Rate Limiting = 滑動窗口 + 分組 key,擋的是「重複的惡意濫用」,不是「低頻真用戶」。
- Turnstile 被動驗證環境信號,把 UX 打斷降到最低,驗證結果由 Worker 端呼叫 siteverify 才生效(client token 不可信)。
練習
- 為
/api/login寫一組完整防護:1 條 Rate limiting rule + 1 條 Managed Challenge 表達式(當cf.threat_score > 30)+ 前端 Turnstile,寫出三者的設定語意與先後順序。 - 從現實借一對「誤殺情境」:若某 API client 用了非常規 User-Agent 常態呼叫,請評估用 skip、challenge 或 allowlist,並說明取捨。
- 關閉 siteverify 步驟(只用前端 widget)會發生什麼安全問題?用一封「重新送出表單 + 偽 token」的 curl 驗證你的推理。