iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
自我挑戰組

一鍵完成六套開源防禦系統整合系列 第 16 篇

WAF第 4 篇 設定、調校與驗證

  • 分享至 

  • xImage
  •  

安裝包裡與 WAF 有關的參數只有幾個;誤判時要改的是哪個檔案、怎麼寫、怎麼讓它生效;怎麼確認 WAF 真的在擋而不只是在轉發;WAF 或後端掛掉時整條鏈會發生什麼;以及哪些事是刻意沒做的。

一、可調的參數

全部在主機 B 的 .env(由 install.sh 產生,root 0600)。改完跑 sudo bash install.sh --reconfigure,它會重新產生設定並重建有變動的容器。

參數 預設 作用 生效方式
WAF_BACKEND_URL 必填 被保護網站的內網位址含埠,如 http://192.168.0.111:8000。填錯的症狀是 WAF 對所有請求回 502 --reconfigure(重建 waf-nginx)
WAF_RULE_ENGINE On On 擋;DetectionOnly 只記錄。後者規則全跑、分數全算、audit.log 全寫、平台事件照送,只是不回 403。上線第一天建議用它觀察誤判 --reconfigure
WAF_PARANOIA 1 CRS Paranoia Level 1~4(第 1 篇第七節)。升級前先 DetectionOnly 跑一段時間 --reconfigure
WAF_BACKEND_PATH 空 歡迎頁與主站同 hostname 時,只有這個路徑前綴導到被保護網站(如 /beakplatform)。只影響 cloudflared ingress,WAF 本身不看它 --reconfigure(重寫 tunnel ingress)
WELCOME_HOSTNAME 空 有值就多起 welcome 與 waf-welcome 兩個容器(總覽第六節) --reconfigure
TRUSTED_INGRESS_EXTRA 空 Vector 歸因時額外信任的直連來源。不是 WAF 的設定,但直接決定 WAF 事件的 actor.ip(第 3 篇第四節) --reconfigure(重啟 vector)
ADMIN_IPS 安裝時自動加入 SSH 來源與平台主機 誰能從內網直打 8080/8082 做驗證。不在名單內的來源連線逾時 --reconfigure(重產 nftables)

兩個不在 .env、要改 docker-compose.yml 才能動的值:ANOMALY_INBOUND=5、ANOMALY_OUTBOUND=4。它們是 CRS 出廠預設,本安裝包刻意不開放成參數:調門檻幾乎一定是錯的解法,誤判該用規則排除處理。

二、覆寫檔:唯一該手改的規則檔案

waf/modsec-overrides/modsecurity-override.conf 在 crs-setup.conf 之前載入(第 1 篇第三節)。出廠內容兩段:一段生效中的方法白名單,一段註解掉的路徑排除範例。

900200:方法白名單,為什麼非有不可

SecAction \
 "id:900200,\
  phase:1,\
  nolog,\
  pass,\
  t:none,\
  setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PUT DELETE PATCH'"

CRS 的 crs-setup.conf 裡這條規則預設是註解掉的,此時 911100 的預設白名單只有 GET、HEAD、POST、OPTIONS。任何 REST API 的 PUT/DELETE/PATCH 都會命中 911100,等級 CRITICAL,一條就 5 分,直接 403。症狀是「網站看起來正常,但所有修改與刪除操作都失敗」,而且從瀏覽器看只是一個 403。本安裝包把它補回並加入三個 REST 方法。要再加方法(例如 WebDAV 的 PROPFIND)就改這一行。

900300:路徑層規則排除的寫法

SecRule REQUEST_URI "@beginsWith /beakplatform/api/notes/" \
 "id:900300,\
  phase:1,\
  nolog,\
  pass,\
  ctl:ruleRemoveTargetByTag=attack-rce;ARGS,\
  ctl:ruleRemoveTargetByTag=attack-injection-php;ARGS,\
  ctl:ruleRemoveTargetByTag=attack-xss;ARGS,\
  ctl:ruleRemoveTargetByTag=attack-sqli;ARGS"
片段 意義
SecRule REQUEST_URI "@beginsWith /…/" 只對這個路徑前綴生效。REQUEST_URI 含查詢字串;只比對路徑用 REQUEST_FILENAME
id:900300 自訂規則 ID。CRS 保留 900000~900999 給使用者的前置規則,不會跟 CRS 撞號;多條排除就 900301、900302 往下編
phase:1 在請求標頭階段就執行,趕在 phase 2 的攻擊規則之前把目標移除。排除規則寫 phase 2 會太晚
nolog, pass 這條本身不記 log、不影響流程
ctl:ruleRemoveTargetByTag=attack-sqli;ARGS 對所有帶 attack-sqli 標籤的規則,把 ARGS 從檢查目標移除。URL、標頭、Cookie 照常檢查,只有參數值不查。這是最常用也最保守的排除方式:SQLi 規則還在,只是不看這個路徑的參數

其他常見的 ctl 寫法,由寬到窄:

寫法 效果 建議
ctl:ruleEngine=Off 整個路徑不檢查 只給健康檢查或內部 API
ctl:ruleRemoveByTag=attack-sqli 整組 SQLi 規則對此路徑停用(所有目標) 過寬
ctl:ruleRemoveById=942100 單一規則對此路徑停用 可接受
ctl:ruleRemoveTargetByTag=attack-sqli;ARGS 整組規則只不看 ARGS 常用
ctl:ruleRemoveTargetById=942100;ARGS:note_body 單一規則只不看某一個參數 最窄,優先

audit.log 的 messages[].details.data 會標出命中的變數名(如 ARGS:note_body),最窄的那種寫法所需的資訊都在裡面。

三件會讓人白忙的事

  1. 覆寫檔是 .template 掛載,容器啟動時才展開。改完要 docker compose up -d --force-recreate waf-nginx waf-welcome 或 --reconfigure;docker compose restart 不會重新展開模板
  2. 語法錯誤時容器起不來,網站直接不通。改完看 docker compose logs waf-nginx,正常會印 rules loaded inline/local/remote: 0/8xx/0
  3. 排除規則不影響已經建立的案件,也不會通知平台。平台上把誤判案件結案是另一件事

三、誤判處理流程

# 步驟 做法
1 先確認是 WAF 擋的 使用者看到的是 nginx 的 403 頁(沒有網站自己的版型);audit.log 該筆 is_interrupted: true。網站自己回的 403 不在 WAF 的責任範圍
2 找到規則與變數 從 audit.log 或 ClickHouse 的 raw 欄位取 messages[]:ruleId、data 裡的 ARGS:xxx、tags 裡的 paranoia-level
3 判斷是不是設計上的必然 「使用者可以輸入 SQL 範例的欄位」「筆記內容含 HTML」這類是應用本身的性質,用排除處理;「表單參數名剛好叫 select」這類是巧合,也用排除處理;兩者都不該改門檻或降 PL
4 寫最窄的排除 路徑前綴 + ruleRemoveTargetById=規則;ARGS:參數,加進覆寫檔,重建容器
5 驗證 重打原本被擋的請求應該通過;用第 1 篇第六節的 SQLi 探測打其他路徑仍應 403,確認排除沒有寫過寬

大量誤判、或準備升 PL 時,先把 WAF_RULE_ENGINE 設成 DetectionOnly 跑一到數天。此時所有本來會被擋的請求都放行、都記 log、都送平台,SOC 在處置中心看到的案件量就是上線後會被擋的量。從 ClickHouse 依 finding_rule_id 與 target_url 分組,逐條決定排除,再切回 On。

四、怎麼確認它真的在擋

「網站打得開」只證明 nginx 在轉發,不證明規則引擎在跑。三個層次,由淺到深:

層次:安裝包自帶的狀態檢查
預期:WAF 那兩列:/ 回 200/3xx/404 都算正常(404 表示後端根路徑沒頁面);SQLi 探測 必須是 403
指令:
sudo bash /opt/integrated-waf/install.sh --status

層次:手動探測(從 ADMIN_IPS 內的機器)
預期:200、403、403、405(最後一個 405 是後端回的,證明 PATCH 通過了 WAF)。若 SQLi 回 200 而 WAF_RULE_ENGINE=On,先看容器 log 有沒有載入規則
指令:
B=http://192.168.0.112:8080
curl -s -o /dev/null -w '%{http_code}\n' "$B/beakplatform/auth/login"
curl -s -o /dev/null -w '%{http_code}\n' "$B/beakplatform/?id=1%27%20OR%201=1--"
curl -s -o /dev/null -w '%{http_code}\n' "$B/beakplatform/?q=%3Cscript%3Ealert(1)%3C/script%3E"
curl -s -o /dev/null -w '%{http_code}\n' -X PATCH "$B/beakplatform/auth/login"

層次:看 audit.log 有沒有跟著多一行
預期:每個探測一行;SQLi 那筆有 942100 與 949110,is_interrupted True。client_ip 是你打的那台機器的 IP
指令:
sudo tail -n 3 /opt/integrated-waf/waf/log/audit.log | python3 -c '
import sys, json
for line in sys.stdin:
t = json.loads(line)["transaction"]
print(t["request"]["method"], t["request"]["uri"][:60], t["response"]["http_code"], t["client_ip"], t.get("is_interrupted"))
for m in t.get("messages", []):
print(" ", m["details"]["ruleId"], m["message"][:70])'

層次:看 Vector 有沒有讀走(密碼在 .env)
預期:探測後幾秒內出現。內網探測的事件會在這裡,但不會進平台(內網對內網被 intake_filter 丟掉),要看端對端建案請用安裝包的 --test-event 或從 Internet 打
指令:
docker compose -f /opt/integrated-waf/docker-compose.yml exec clickhouse
clickhouse-client --user secstack --password "$CLICKHOUSE_PASSWORD" --query
"SELECT event_time, finding_rule_id, IPv6NumToString(actor_ip), target_url
FROM secstack.events WHERE source_system='coraza'
ORDER BY event_time DESC LIMIT 5"

五、掛掉時會發生什麼

掛掉的是 網站還通嗎 還會擋嗎 其他影響
waf-nginx 容器 不通 — inline 的代價:WAF 是請求路徑上的一站,它掛了就是斷線。cloudflared 回 502/530,內網 8080 連線被拒。restart: unless-stopped 會自動拉起來,起不來多半是覆寫檔語法錯或後端位址格式錯
被保護網站 502 會 WAF 對每個請求回 502 並記 audit.log(5xx 是相關狀態)。平台會收到一批 rule_id 空、severity Low、actor 各不相同的事件,量由封頂控制。這些案件不該封 IP,訪客是無辜的;單人版的自動封鎖門檻是 severity ≥ 4,這類到不了
Vector 通 會 audit.log 照寫。Vector 重啟後從 checkpoint 續讀,中間的行不會丟;但若停機超過一小時,輪替會把還沒讀的行搬到 .YYYYMMDD-HH 檔,Vector 不讀輪替檔,那一小時的事件只剩 gz 裡的原始 log
od-bridge 或平台 通 會 WAF 完全不受影響。事件進 ClickHouse,送平台那一路失敗即丟(sink 無緩衝)。攻擊當下的阻擋一件都不會少,少的是案件與封鎖
cloudflared 對外不通,內網 8080 通 會 WAF 只是收不到 Internet 流量。熱備切換是另一份文件的主題

六、刻意沒做的事

沒做的事 理由或現況
速率限制、登入暴力破解偵測 CRS PL1 沒有速率規則,本安裝包也沒開 nginx limit_req。長期的機械式攻擊(撞庫、爬蟲)建議從 ClickHouse 統計後封 IP,在防火牆丟掉比在規則引擎逐請求比對便宜。平台端只能靠併案計數看出重複
TLS 終結 容器有 8443 與自簽憑證,但沒發布。走 Cloudflare 時 TLS 在 edge 終結;不走 Cloudflare 的內網部署本來就是明文。要自己終結 TLS 的讀者可在 compose 發布 8443 並換憑證,但那不在本安裝包的測試範圍
規則自動更新 映像檔是浮動 tag owasp/modsecurity-crs:nginx,安裝時拉到什麼就是什麼。更新規則=docker compose pull waf-nginx && docker compose up -d waf-nginx,會同時換 nginx、引擎與 CRS 版本,升級後要重跑第四節的探測,因為新版 CRS 可能多出新規則造成新的誤判
把平台的封鎖清單推回 WAF WAF 逐請求判斷,沒有「這個 IP 直接拒絕」的清單。封鎖落地點是 nftables/CrowdSec/EDL。要在 WAF 前面擋掉已封鎖的 IP,靠的是邊界防火牆抓 EDL(原系列第 6 篇)
GeoIP 封鎖 映像檔有 nginx 的 geoip 模組,但沒有資料庫也沒有設定。國別資訊在 Cloudflare 的 Cf-Ipcountry 標頭,平台事件的 actor.country 從那裡來,要依國別處置在平台流程裡做
JSON 回應的外洩檢查 SecResponseBodyMimeType 只含 text/plain、text/html、text/xml。API 的錯誤訊息若含 SQL 錯誤或堆疊,WAF 看不到。這是 CRS 的預設,本安裝包沒有擴充
CRS 外掛 setup.conf 有掛載點但沒裝任何外掛(如 WordPress/Nextcloud 的排除包)。被保護網站是這類應用時,讀者可自行放進 plugins/

七、常見問題

症狀 原因與處理
SQLi 探測回 200 先確認打的是 8080 不是後端埠;再看 .env 的 WAF_RULE_ENGINE 是不是 DetectionOnly;再看 docker compose logs waf-nginx 有沒有 rules loaded
正常請求全部 502 WAF 連不到 WAF_BACKEND_URL。在主機 B 上直接 curl 那個位址;被保護網站若有 IP 白名單要放行主機 B
正常請求回 000(無回應) 後端對根路徑不回應,改測實際頁面路徑。BeakPlatform 的根路徑就是這樣
所有 PUT/DELETE/PATCH 都 403 覆寫檔的 900200 沒生效,多半是改了檔案但沒重建容器。audit.log 會看到 911100
從內網打 8080 連線逾時 來源不在 ADMIN_IPS,被 nftables drop。改 .env 後 --reconfigure。逾時與 403 是兩件事:逾時代表根本沒到 WAF
ClickHouse 一堆 920350 事件 有人用 IP 直打 8080。不是攻擊,改用 hostname 或接受它
平台上沒有內網探測的案件 預期行為:內網 actor 對內網 target 在 Vector 就被丟掉。端對端驗證用 --test-event(走合成事件注入口)或從 Internet 打
改了覆寫檔後容器一直重啟 規則語法錯誤。docker compose logs waf-nginx 會印出是哪一行;常見是反斜線續行後面多了空白
升級映像後突然多了誤判 新版 CRS 加了規則。從 audit.log 的 ver 欄位確認版本,依第三節處理
audit.log 擁有者顯示成奇怪的帳號名 容器內 nginx 的 uid 101 對應到主機上剛好占用 101 的帳號,正常

上一篇
WAF第 3 篇 audit.log:WAF 與平台之間唯一的線
下一篇
防禦主機的流程分叉點--分成:線上阻擋與線外處置
系列文
一鍵完成六套開源防禦系統整合 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言