iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
自我挑戰組

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

WAF 元件篇:nginx + ModSecurity v3 + OWASP CRS(一)

  • 分享至 

  • xImage
  •  

這篇WAF是由三個repo組成,會分五篇說明,偏重與BeakPlatform的關係

防禦節點(主機 B)上那個叫 waf-nginx 的容器裡裝的是什麼、它在整套架構裡負責哪一段、不負責哪一段。本系列共五篇,本篇是總覽。Suricata 與 CrowdSec 另有專篇,這裡只在需要對照時提到。

閱讀順序
1. 總覽:一個容器、三個名字、一條單向的線(本篇)
2. 元件與規則引擎:nginx、ModSecurity v3、OWASP CRS 各做什麼,設定怎麼疊起來,分數怎麼算
3. 一個請求在 WAF 容器內的旅程:五個階段、403 由誰回、轉給後端時帶什麼、為什麼 real_ip 刻意失效
4. audit.log:什麼時候寫、一行長什麼樣、Vector 怎麼把它變成平台事件
5. 設定、調校與驗證:可調的參數、誤判怎麼排除、怎麼確認它真的在擋、掛了會怎樣
上一層是 防禦節點運作原理(九篇),本系列的主機、IP 與線型慣例都沿用那一套:主機 A(192.168.0.111)是 BeakPlatform,主機 B(192.168.0.112)是防禦節點,攻擊者用文件保留位址 203.0.113.42。

一、一句話定位

在整條鏈上,WAF 是唯一站在請求路徑上、讀得到完整 HTTP 內容、而且能當場把請求擋下來的元件。nftables 只看 IP 與埠,Suricata 只能旁觀不能丟包,CrowdSec 看的是時間軸上的行為,平台則要等事件送到才知道有事。攻擊者送出 ?id=1' OR 1=1-- 的那幾十毫秒內,唯一做出反應的就是這個容器:回 403,請求不再往後走。

但它的責任也在這裡結束。擋掉之後它留下的只有 audit.log 裡的一行 JSON,「這個 IP 是不是累犯」「要不要封它」「封多久」「誰負責」這些問題它一概不知道,也沒有機制去知道。這些是平台補上的部分,不是 WAF 做不好。

二、三個名字、一個容器

讀者常把「WAF」當成一個軟體,實際上 waf-nginx 容器裡是三個各自獨立的專案疊在一起,分工非常清楚。下表的版本是本文撰寫時(2026-09-26)在防禦節點上實測的;映像檔 owasp/modsecurity-crs:nginx 是浮動 tag,安裝時拉到什麼版本就是什麼版本,第 4 篇會談這件事的後果。

名字 實測版本 它是什麼 類比
nginx 1.30.5 Web 伺服器兼反向代理。收 TCP 連線、解析 HTTP、把請求轉給被保護網站、把回應送回去。它本身不懂什麼叫攻擊 門口的櫃台:收信、轉交、回信
ModSecurity v3(libmodsecurity 3.0.16 + ModSecurity-nginx connector 1.0.4) 3.0.16 / 1.0.4 規則引擎。提供一套規則語言(SecRule)、變數、轉換函式、運算子與動作,並在請求與回應的固定階段執行規則。它本身不帶任何規則,空跑時什麼都不擋。connector 是把引擎掛進 nginx 生命週期的膠水 安檢儀器:有掃描能力,但要有人告訴它什麼算違禁品
OWASP Core Rule Set(CRS) 4.25.1,850 條 規則內容。SQL injection、XSS、命令注入、路徑穿越、掃描器指紋、協定畸形、回應外洩等的偵測規則,加上一套「異常分數」計分與攔截機制。CRS 是社群維護的通用規則,不認識任何特定應用 違禁品清單與判定門檻

同一個 CRS 也能跑在 Coraza、Apache 的 mod_security2 或其他相容引擎上;同一個 libmodsecurity 也能載入自己寫的規則。三者是可以替換的,本安裝包選的是 OWASP 官方維護的 nginx 版容器映像,理由是零編譯、設定全部靠環境變數、規則與引擎版本由上游一起配好。

三、在架構中的位置

下圖只畫與 WAF 直接相連的東西。實線是請求路徑,藍色虛線是事件路徑,紅色點線是決策路徑。注意紅線沒有任何一條回到 WAF。
https://ithelp.ithome.com.tw/upload/images/20260927/20184261dm4iOPujNe.png

四、責任切分

事情 WAF 做嗎 說明
判斷單一請求是不是攻擊 主責 依 CRS 規則對請求與回應打分數,達門檻即攔下。這是整條鏈唯一的即時阻擋點(走 Cloudflare 時 edge 另有一層,不屬本安裝包)
回 403 給攻擊者 主責 由 nginx 回,後端完全不知道有這個請求
留下證據 主責 audit.log 一行 JSON:命中的規則、分數、請求標頭、方法、URI、直連來源
把請求轉給被保護網站 主責 放行的請求由 nginx proxy_pass 到 WAF_BACKEND_URL,並補上 X-Forwarded-* 標頭
判斷「攻擊者真實 IP」是誰 不做 WAF 記的是直連它的對象(cloudflared 容器或 docker 網關)。要不要相信 CF-Connecting-IP 是 Vector 的事,理由見第 2 篇第六節
記得「這個 IP 剛才已經被擋 50 次」 做不到 WAF 沒有跨請求記憶,每次都當第一次。累犯判斷由平台的併案計數與風險分數補上
封鎖來源 IP 做不到 它只能逐請求 403。封 IP 是平台決策 → od-bridge 落地到 nftables/CrowdSec/EDL,WAF 不在落地點清單裡
速率限制、登入暴力破解偵測 沒有 CRS Paranoia Level 1 沒有速率規則;本安裝包也沒開 nginx 的 limit_req。這是已知缺口,見第 4 篇第六節
TLS 終結 不做 容器有 8443 監聽(自簽憑證)但 compose 沒有發布它。走 Cloudflare 時 TLS 在 edge 終結,tunnel 內是明文 HTTP 進 WAF
決定哪些事件要送去建案 不做 WAF 只管寫 log。過濾、歸因、封頂全在 Vector
更新規則 不會自動 規則跟著映像檔走,重拉映像才會換版本

五、WAF 與平台之間只有一條線,而且是單向的

圖 0-1 值得盯著看的是線的方向。WAF 與平台之間只有 audit.log 這一條線,方向是 WAF → Vector → od-bridge → 平台。平台不會回頭做任何事:不會改 WAF 的規則、不會把封鎖清單推回 WAF、不會叫 WAF 放行某個 IP。三個直接推論:

推論 後果
平台掛了,WAF 照擋 攻擊當下的阻擋 100% 在防禦節點內完成,不依賴主機 A 活著。平台不在時損失的是「擋掉之後」的那些事:沒有案件、沒有封鎖、沒有解封
平台核可的封鎖,對「經 Cloudflare 進來的請求」在 WAF 這一層沒有效果 封鎖落地在 nftables input hook,但 tunnel 流量是 cloudflared 主動連出的既有連線,封包來源是 Cloudflare。被封的 IP 再打一次,還是由 WAF 逐請求判斷、逐請求 403。要在邊界擋掉得靠 EDL 接外部防火牆(原系列第 5、6 篇)
WAF 誤判時,要改的是 WAF 自己的覆寫檔,不是平台 平台上把案件標成誤判、放行,都不會讓下一個同樣的請求通過 WAF。規則排除寫在主機 B 的 waf/modsec-overrides/modsecurity-override.conf(第 4 篇第二節)

六、其實有兩個 WAF 容器

設定了歡迎頁(WELCOME_HOSTNAME)時,compose 會多起兩個容器:welcome(一頁靜態 HTML 的 nginx)與 waf-welcome(用同一個映像、同一組參數的第二個 WAF,後端指向 welcome)。cloudflared 的 ingress 依 hostname 或路徑前綴決定送哪一個。這樣做的目的是讓「刺探根路徑與亂猜的路徑」也經過 WAF 而產生事件,而不是直接 404 掉。兩個容器的差別只有三處:

項目 waf-nginx(主站) waf-welcome(歡迎頁)
後端 WAF_BACKEND_URL(被保護網站) http://welcome:80(容器內靜態頁)
audit log waf/log/audit.log waf/log/audit-welcome.log
事件的 target.service waf-nginx waf-welcome(Vector 依檔名判斷)
內網驗證埠 8080 8082

面各篇講的規則、階段、log 格式對兩個容器完全相同,不再分開說。

七、與原系列的對應

本系列 原系列(防禦節點運作原理)已經談過的部分
第 1 篇 元件與規則引擎 第 2 篇第二節的規則群表;本篇把引擎、載入順序、計分方式補齊
第 2 篇 請求旅程 第 1 篇第 4 站;本篇把五個階段、轉送標頭、real_ip 的設計理由展開
第 3 篇 audit.log 第 3 篇第一節的 Vector 管線;本篇從 log 本身的格式與寫入條件出發
第 4 篇 設定與驗證 安裝章節第七節的日常操作;本篇只談 WAF 相關的參數、排除與故障

上一篇
nftables 在防禦節點上的角色:一張表、三個集合、四種責任
下一篇
WAF 第 1 篇 元件與規則引擎
系列文
一鍵完成六套開源防禦系統整合 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言