WAF 完整規則手冊
從零開始寫 WAF 規則,涵蓋全部運算子 / 動作 / 常見場景配方。
從零開始寫 WAF 規則,涵蓋全部運算子 / 動作 / 常見場景配方。
TL;DR 三條黃金原則:
- 白名單放最前面(優先級小數字),保證信任流量永遠通過
- 新規則先
log後block,看一週命中再決定是否升級- 預設 6 條規則保留,涵蓋了 80% 攻擊面
一、規則結構
每條 WAF 規則 5 個欄位:
{
"name": "規則名稱(顯示用)",
"conditions": [/* 條件群組陣列,見下文 */],
"action": "block",
"blockStatus": 403,
"blockBody": "Blocked by FurCDN WAF",
"priority": 10,
"enabled": true
}評估流程
- 規則按
priority數字小的先評估 - 條件命中 → 執行 action
- action =
allow→ 立即放行,跳過所有後續規則 - action =
block/challenge→ 立即阻斷,跳過所有後續 - action =
log/rateLimit不命中 → 繼續評估下一條規則 - 全部規則都沒命中 → 預設放行
二、5 種 Action
1. allow(白名單)
命中即放行,跳過後續所有規則。常見用途:
- 公司辦公室 IP 永遠通過
- 監控掃描器 IP 不被誤封
- 內部 API 客戶端跳過 WAF
{
"name": "公司白名單",
"priority": 1,
"action": "allow",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "ip", "operator": "cidr", "value": "203.0.113.0/24"}
]
}]
}⚠️
allow是「徹底信任」,放行後連 rate limit、challenge、預設 block 都跳過。要謹慎用。
2. log(記錄)
命中只寫日誌不影響流量。新規則上線前先 log 看誤判率。
{
"name": "可疑 UA(觀察)",
"priority": 100,
"action": "log",
"conditions": [{
"logic": "OR",
"conditions": [
{"field": "header:user-agent", "operator": "contains", "value": "curl"},
{"field": "header:user-agent", "operator": "contains", "value": "wget"}
]
}]
}跑一週後到「訪問日誌」搜命中,確認沒誤判再改 block。
3. block(阻斷)
最常用。命中回 blockStatus(預設 403)+ blockBody。
{
"name": "禁止 .git 訪問",
"priority": 5,
"action": "block",
"blockStatus": 404,
"blockBody": "Not Found",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "starts_with", "value": "/.git"}
]
}]
}用 404 而非 403 可以「裝沒這個資源」,讓掃描器以為路徑不存在。
4. rateLimit(速率限制)
命中後計數,超過閾值在窗口內封鎖。3 個額外參數:
| 參數 | 說明 |
|---|---|
rateLimitCount | 視窗內最大請求數 |
rateLimitWindow | 視窗長度(秒) |
rateLimitBlock | 觸發後封鎖時長(秒) |
範例:登入端點 1 分鐘 5 次,超過封 10 分鐘:
{
"name": "登入速率限制",
"priority": 8,
"action": "rateLimit",
"rateLimitCount": 5,
"rateLimitWindow": 60,
"rateLimitBlock": 600,
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "starts_with", "value": "/api/login"},
{"field": "method", "operator": "equals", "value": "POST"}
]
}]
}計數 key 是
clientIP + rule.id,每條規則獨立計數。
5. challenge(挑戰閘道)
命中 IP 進入「JS 挑戰頁」,通過後簽 cookie 30 分鐘有效:
- 真實使用者幾乎無感(
<1 秒解 PoW + 自動跳轉) - 機器人/爬蟲無法通過(沒有 JS runtime)
最佳場景:輕度 CC 攻擊(高頻但 IP 分散),或「對全站可疑流量加道牆」。
{
"name": "海外可疑流量挑戰",
"priority": 50,
"action": "challenge",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "country", "operator": "in", "value": ["RU", "KP"]},
{"field": "uri", "operator": "starts_with", "value": "/api/"}
]
}]
}三、10 種 Field
uri 請求路徑
不含 query string。範例 /api/login / /admin/users。
query 查詢字串
不含 ?。範例 id=1&token=abc。
header:<name> 指定 HTTP header
name 大小寫不敏感。常用:
header:user-agentheader:refererheader:cookieheader:hostheader:x-forwarded-for(若使用者開了realIpHeader,這個是真實 IP 之前的鏈路)
method
GET / POST / PUT / DELETE / OPTIONS / HEAD / PATCH
ip
已套用 realIpHeader 的真實客戶端 IP。
referer
Referer header(header:referer 的快捷方式,單獨拿出來方便用)。
body
請求 body。有大小限制:
- 預設只讀前 64 KB
- 大檔上傳(超過上限)的 body 不被讀,規則自動視為不命中
country
ISO 3166-1 alpha-2 國家碼:CN / US / JP / RU / KP 等。
透過 ipgeo 查得(海外用 ipinfo,國內用 qqwry)。
asn
ASN 編號(數字),如 15169(Google)/ 13335(Cloudflare)。
範例:綜合多 field
{"field": "uri", "operator": "starts_with", "value": "/api/admin"}
{"field": "header:user-agent", "operator": "contains", "value": "Bot"}
{"field": "ip", "operator": "cidr", "value": "10.0.0.0/8", "negate": true}
{"field": "country", "operator": "equals", "value": "CN"}
{"field": "asn", "operator": "in", "value": [15169, 13335]}四、10 種 Operator + Negate
contains
子字串包含。大小寫不敏感。
{"field": "header:user-agent", "operator": "contains", "value": "sqlmap"}equals
完全相等。大小寫敏感(field 為 ASCII 字串時)。
regex
RE2 正則(Go 標準庫,不支援 backreference / lookahead)。
{"field": "query", "operator": "regex", "value": "(?i)union\\s+select"}⚠️ 正則表達式效能低於字串比對。能用
contains/starts_with就不要用 regex。
not_contains / not_equals
否定版本。等同 contains / equals + negate: true,擇一使用。
starts_with / ends_with
前綴 / 後綴匹配。
{"field": "uri", "operator": "starts_with", "value": "/api/v1/"}
{"field": "uri", "operator": "ends_with", "value": ".php"}in
值在列表中。value 為陣列。
{"field": "country", "operator": "in", "value": ["RU", "KP", "IR"]}
{"field": "method", "operator": "in", "value": ["DELETE", "PATCH"]}cidr
IP 落在 CIDR 範圍。value 為單一 CIDR 字串或字串陣列。
{"field": "ip", "operator": "cidr", "value": "192.168.0.0/16"}
{"field": "ip", "operator": "cidr", "value": ["1.2.3.0/24", "5.6.7.8/32"]}支援 IPv4 + IPv6。
threat
命中威脅情報庫(無需 value)。
{"field": "ip", "operator": "threat"}聚合 4 個來源:Spamhaus DROP/EDROP、FireHOL Level 1、Tor 出口、本平台自反饋。
negate: true
所有 operator 都可加 negate: true 取反。一個 flag 通用,不需另外學「not_xxx」。
{"field": "ip", "operator": "cidr", "value": "1.2.3.0/24", "negate": true}
// 等同「IP 不在 1.2.3.0/24」五、條件群組與邏輯連接
群組(Group)
一條規則可有多個 group,group 之間是 OR(任一命中即規則命中)。
群組內條件(Conditions)
每個 condition 帶 connector(AND / OR),從左到右逐條合併:
{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "starts_with", "value": "/admin/"},
{"field": "ip", "operator": "cidr", "value": "1.2.3.0/24", "negate": true, "connector": "AND"},
{"field": "header:authorization", "operator": "equals", "value": "", "connector": "OR"}
]
}評估順序:
(uri starts /admin/)... AND (ip not in 1.2.3.0/24)... OR (no auth header)
無運算子優先級,完全是左到右合併。要強制邏輯就用多個 group 拆。
第一條的 connector 忽略
第一條 condition 的 connector 不被讀(沒有「前一條」可合併)。fallback 到 group.logic(舊資料相容)。
多 group 範例:「管理路徑 AND 不在白名單」OR「敏感 query」
{
"conditions": [
{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "starts_with", "value": "/admin/"},
{"field": "ip", "operator": "cidr", "value": "1.2.3.0/24", "negate": true}
]
},
{
"logic": "AND",
"conditions": [
{"field": "query", "operator": "regex", "value": "(?i)password|secret|token"}
]
}
]
}兩個 group 之間是 OR:管理路徑非白名單 或 query 含敏感關鍵字 → 命中。
六、預設 6 條規則(新建域名自動產生)
| 規則 | 條件 | 動作 |
|---|---|---|
| 掃描器攔截 | header:user-agent contains nmap / sqlmap / nikto / acunetix / nessus | block 403 |
| 空 User-Agent | header:user-agent equals "" | block 403 |
| SQL 注入 | query/body regex (?i)(union\s+select|drop\s+table|insert\s+into) | block 403 |
| XSS | query/body regex (?i)<script|javascript:|onerror= | block 403 |
| 路徑穿越 | uri contains ../ | block 403 |
| 惡意 Referer | referer contains 已知惡意網站列表 | block 403 |
保留預設規則(可細調但別全砍),這些是最低標準防護。
七、常見場景配方
1. 公司辦公室白名單
{
"name": "辦公室白名單",
"priority": 1,
"action": "allow",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "ip", "operator": "cidr", "value": ["203.0.113.0/24", "198.51.100.0/24"]}
]
}]
}2. 封鎖整個國家
{
"name": "封鎖俄朝伊",
"priority": 10,
"action": "block",
"blockStatus": 403,
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "country", "operator": "in", "value": ["RU", "KP", "IR"]}
]
}]
}3. 命中威脅情報庫即封
{
"name": "已知惡意 IP",
"priority": 5,
"action": "block",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "ip", "operator": "threat"}
]
}]
}4. API 速率限制
{
"name": "API 速率限制",
"priority": 20,
"action": "rateLimit",
"rateLimitCount": 60,
"rateLimitWindow": 60,
"rateLimitBlock": 300,
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "starts_with", "value": "/api/"}
]
}]
}每 IP 1 分鐘 60 次,超過封 5 分鐘。
5. 登入端點嚴格限速
{
"name": "登入嚴格限速",
"priority": 15,
"action": "rateLimit",
"rateLimitCount": 5,
"rateLimitWindow": 60,
"rateLimitBlock": 1800,
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "equals", "value": "/api/auth/login"},
{"field": "method", "operator": "equals", "value": "POST"}
]
}]
}每 IP 1 分鐘 5 次,觸發封 30 分鐘。
6. 防 Hotlink(熱連結盜圖)
{
"name": "圖片防盜鏈",
"priority": 30,
"action": "block",
"blockStatus": 403,
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "regex", "value": "\\.(jpg|jpeg|png|gif|webp)$"},
{"field": "referer", "operator": "starts_with", "value": "https://example.com", "negate": true},
{"field": "referer", "operator": "equals", "value": "", "negate": true}
]
}]
}只允許 https://example.com 來源訪問圖片(空 Referer 也擋)。
7. 海外可疑流量加挑戰
{
"name": "海外加挑戰",
"priority": 40,
"action": "challenge",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "country", "operator": "equals", "value": "CN", "negate": true}
]
}]
}8. WordPress / phpMyAdmin 路徑掃描器
{
"name": "PHP 掃描器擋",
"priority": 5,
"action": "block",
"blockStatus": 404,
"conditions": [{
"logic": "OR",
"conditions": [
{"field": "uri", "operator": "ends_with", "value": ".php"},
{"field": "uri", "operator": "contains", "value": "/wp-admin/"},
{"field": "uri", "operator": "contains", "value": "/wp-login.php"},
{"field": "uri", "operator": "contains", "value": "/phpmyadmin/"}
]
}]
}如果你用的不是 PHP / WordPress,所有這些 URL 都是掃描器探測。404 偽裝路徑不存在。
9. 「只允許特定 ASN」
{
"name": "只允許 ISP 訪問",
"priority": 100,
"action": "block",
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "asn", "operator": "in", "value": [4134, 4837, 9808], "negate": true}
]
}]
}4134 = 中國電信,4837 = 中國聯通,9808 = 中國移動。其他 ASN 全擋。
10. 「沒有 Cookie 就攔截特定路徑」
{
"name": "需登入",
"priority": 25,
"action": "block",
"blockStatus": 401,
"conditions": [{
"logic": "AND",
"conditions": [
{"field": "uri", "operator": "starts_with", "value": "/dashboard/"},
{"field": "header:cookie", "operator": "contains", "value": "session_id=", "negate": true}
]
}]
}八、威脅情報庫
來源
| 來源 | 內容 | 更新頻率 |
|---|---|---|
spamhaus_drop | Spamhaus 全球已知惡意網段 | 4 小時 |
spamhaus_edrop | Extended DROP(更廣) | 4 小時 |
firehol_level1 | FireHOL 一級惡意 IP(最嚴格) | 4 小時 |
tor_exit | Tor 出口節點 | 4 小時 |
self_feedback | 本平台 24h 高頻失敗 IP(自反饋) | 即時 |
在 WAF 中使用
{"field": "ip", "operator": "threat"}任何來源命中即視為惡意。
使用者唯讀瀏覽
儀表板 > 威脅情報庫:
- 搜尋 IP / CIDR
- 過濾來源
- 包含過期項(可選)
- 限速 60 次 / 分鐘(防爬)
管理員額外能做的
- 手動加白(刪除某條惡意紀錄)
- 立即觸發刷新拉所有來源
- self_feedback 來源不會自動重加(避免循環)
九、故障排查
規則寫了但不生效
- 域名 WAF 開了嗎?域名編輯 > 基本設定 > WAF 開關
- 規則的
enabled=true嗎? - 優先級對嗎?可能被前面的
allow跳過了 - 看訪問日誌:該請求的「WAF 命中規則」欄位,有就是命中,沒就是真的沒匹配上
- field 拼寫:
header:User-Agentvsheader:user-agent(後者才對,小寫)
誤封正常使用者
- 看訪問日誌篩選 status
403/429/503 - 看哪條規則命中
- 改成
log觀察一週,或加allow白名單 - 規則可能太嚴(如 SQL 注入規則誤判含
select關鍵字的正常 query)
Rate limit 觸發後永遠進不來
rateLimitBlock是封鎖時長(秒),設太大就會封很久- 計數 key 是
clientIP + rule.id,所以用戶換 IP 即可繞 - 短時間想解封:暫停規則 → 觸發過的封鎖會自動 expire
Challenge 通過後又被擋
challengecookie 30 分鐘有效;30 分鐘後再來會重新挑戰- 若使用者瀏覽器禁 cookie,會永遠在挑戰循環
規則太多影響性能
- 預設規則跑全部 6 條 + 自訂規則:單次 WAF 評估
<1ms,通常不是瓶頸 - 真有性能問題:減少
regex規則,改用contains/starts_with bodyfield 規則只在 POST/PUT 觸發,GET 不影響
看不到想要的場景?開工單講具體需求,我們幫你寫規則。