iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
自我挑戰組

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

防禦主機的流程分叉點--分成:線上阻擋與線外處置

  • 分享至 

  • xImage
  •  

總覽的圖 0-1 用三種線型畫資料流。第一次看的人常把它讀成一條由外到內的串列:封包過了 nftables 到 WAF,過了 WAF 到 Suricata、CrowdSec、Vector,最後進平台,像一排濾網。實際上在同一條線上的只有封鎖執行點、WAF 與被保護網站,其餘元件都在另一條線。本篇說明這兩條線怎麼分、為什麼要分、分開之後付出什麼代價。前八篇講的是每一站做什麼,這一篇只回答「為什麼這樣排」。

先講結論。請求路徑是線上(inline):每個請求都要經過,當場決定放行或擋下,它停了網站就停。事件路徑與決策路徑合起來是線外(out-of-band):從 WAF 寫下的紀錄開始,不碰請求本身,慢了或停了都不影響網站。拆開的理由有六個:時間尺度、故障隔離、看得到的資訊、決定的性質、能不能丟資料、攻擊面。代價只有一個方向:封鎖來源 IP 這件事一定比攻擊晚到。

一、兩條線怎麼分

總覽列的是三條資料流。把它們依「會不會擋在請求前面」重新分組,就是兩條線:

總覽的資料流 屬於哪條線 在這條線上的元件
請求路徑(實線) 線上 Cloudflare、cloudflared、nftables(直連主機 B 的封包)、waf-nginx、被保護網站
事件路徑(藍色虛線) 線外:同一個迴圈的去程與回程 Suricata、Vector、ClickHouse、od-bridge 的轉送迴圈、BeakPlatform 的事件受理與建案
決策路徑(紅色點線) 線外:同一個迴圈的去程與回程 BeakPlatform 的簽核與決策、od-bridge 的落地迴圈、CrowdSec LAPI、EDL 檔

https://ithelp.ithome.com.tw/upload/images/20261001/20184261zGpIadLkLb.png

圖上最值得看的是兩個橘色方塊。兩條線之間沒有任何函式呼叫或網路請求,只靠兩種「一邊寫、另一邊讀」的東西接起來:

交接點 誰寫 誰讀 含義
紀錄檔
audit.log 線上的 WAF。判定做完、回應送出之後,才在 ModSecurity 第 5 階段寫下一行 JSON 線外的 Vector,以檔案為來源逐行讀 線外拿到的永遠是「已經發生過的事」。它來不及、也沒有管道回頭改這個請求的結果
封鎖清單
nftables blocklist、EDL 檔、CrowdSec LAPI 線外的 od-bridge,依平台決策加入或移除元素 線上的封鎖執行點:主機 B 的 kernel、抓 EDL 的邊界防火牆、接 LAPI 的 bouncer 線外影響線上的唯一方式是改這份清單。執行點查表只要一次比對,不必等任何人

Suricata 是第三種接法:它用 af-packet 直接抄一份實體網卡上的封包,連紀錄檔都不經過。但它只能看、不能丟包(第 2 篇第三節),所以仍然屬於線外。它不在 WAF「後面」,而是在 WAF 轉給後端的那一跳「旁邊」。

兩條線的性質對照如下,後面六節逐列展開:

# 線上 線外
處理的對象 眼前這一個請求 已經發生過的事的紀錄
時間要求 毫秒 秒到小時
停掉的後果 網站不通 網站照常、WAF 照擋;少的是建案與新的封鎖
看得到什麼 單一請求的完整內容 跨時間、跨來源的累積,加上企業自己的脈絡
決定什麼 這個請求放不放 這個來源要不要封、封多久、誰負責
誰決定 規則 人或流程
能不能丟資料 不能,丟了就是請求失敗 能,而且刻意丟(封頂)

二、理由一:時間尺度不同

第 1 篇第四節的時間軸把一次 SQL injection 從頭走到尾。把它拆成兩列對齊來看,差距很明顯:
https://ithelp.ithome.com.tw/upload/images/20261001/20184261tPElhjva6J.png

WAF 對一個請求的判定要在數十毫秒內完成,因為訪客正在等回應。「這個來源要不要封」則需要併案、算風險分數、可能還要等人看過,高危案件的 SLA 是 15 分鐘。兩件事差了好幾個數量級。

如果把它們排成一條線,也就是「平台點頭之後請求才放行」,結果只有兩種:每個請求都等平台,網站慢到不能用;或者平台被迫在毫秒內回答,那它能做的判斷就退化成查表,簽核與流程都沒有存在的空間。拆開之後,線上只做來得及做的事,需要時間的事全部移到線外,兩邊各用各的節奏。

三、理由二:故障要隔離

第 5 篇第七節逐一列了各元件掛掉的後果。依兩條線重新整理,規律只有一條:
https://ithelp.ithome.com.tw/upload/images/20261001/20184261HgxXpmvWbN.png

線外任何一個元件停掉,訪客感覺不到。這不是運氣,是交接點選「檔案」的直接結果:WAF 只管往檔案寫,不知道也不在乎有沒有人在讀。Vector 停一小時,WAF 不會變慢、不會排隊、不會報錯。如果交接點是一次網路呼叫(WAF 每擋一次就通知平台),平台變慢時 WAF 的工作程序就會卡在等待上,線外的故障會一路傳回線上。

反過來說,線上的元件越少越好。每多放一個元件到線上,就多一個停了會讓網站停的點。這也是 Suricata 採旁路、CrowdSec 不裝 bouncer、平台不站在請求前面的共同理由。

被保護網站剛好就是平台自己的情況。總覽提過被保護的網站可以是主機 A 上的 BeakPlatform。這時平台停了網站當然不通,但原因是它身兼「後端」,不是因為它是「管制端」。兩個角色仍然分屬兩條線,只是跑在同一台主機上。

四、理由三:看得到的資訊不同

WAF 一次判斷一個 HTTP 交易,沒有跨請求的記憶(第 2 篇第二節)。對它來說,同一個 IP 的第 1 次與第 500 次攻擊沒有差別,兩次都是「這個請求的異常分數超過門檻」。這是線上元件的本質限制:要在毫秒內回答,就只能看手上這一個請求。

「要不要封這個來源」需要的資訊恰好都不在單一請求裡:

需要的資訊 為什麼線上拿不到 線外怎麼拿到
這個來源過去一小時打了幾次、用了幾種手法 WAF 不記得上一個請求 平台把同 IP 同規則 60 分鐘內的事件併成一案,累計事件數與風險分數
別的偵測系統有沒有看到同一個來源 WAF 與 Suricata 互不知道對方的存在 兩者的告警在 Vector 統一成同一種格式,案件頁的跨系統關聯再回頭查 ClickHouse
這個 IP 是不是自己人 防禦節點不知道哪個 IP 是辦公室出口、哪個是合作廠商 平台端的封鎖保護清單,寫決策前比對(第 7 篇)
現在誰值班、這種案件該誰決定 與 HTTP 無關 平台的角色、班表與簽核流程

所以兩條線不是「同一件事做兩次」。線上回答的是「這個請求有沒有問題」,線外回答的是「這個來源有沒有問題」,兩個問題需要的輸入不同。

五、理由四:決定的性質不同

# 擋一個請求(線上) 封一個來源(線外)
影響範圍 這一個請求 這個 IP 或網段在 TTL 內的所有連線,包含正常的
判錯的後果 一個 403,使用者重試或回報 整個辦公室、整個 NAT 出口後面的人都連不上,持續到解封
多久重新判斷 下一個請求就重新判斷 要有人解封或等 TTL 到期
需要的配套 規則與門檻 保護清單、負責人、期限、紀錄、解封機制
適合誰做 規則,全自動 人,或事先定好邊界的流程

擋一個請求判錯了,代價小而且立刻有下一次機會,可以放心交給規則。封一個來源判錯了,代價大而且會持續,所以要有保護清單防止封到自己人、要有期限、要留下誰在什麼時候依據什麼決定的紀錄。這些配套正是平台提供的東西(第 3 篇第三節到第五節),它們全部需要時間與脈絡,只能放在線外。

線外不等於人工。出廠的小企業單人版流程在非上班時段不等人,建案後直接寫下 24 小時的封鎖決策(第 3 篇第四節)。它仍然在線外:決策一樣要過保護清單、一樣帶 TTL、一樣留紀錄、一樣由 od-bridge 拉回去落地。「自動」改變的是圖 9-2 中間那一格的長度,不是這件事在哪條線上。

六、理由五:線外可以丟資料,線上不行

Vector 送往平台的那一路有三道封頂(第 3 篇第一節):同來源同規則同 IP 的 WAF 事件 5 分鐘內最多 5 筆,Suricata 事件 1 小時內最多 1 筆,最後全域每 60 秒最多 8 筆。超過的直接丟棄,不排隊、不聚合。同一個 IP 用同一條規則打 1000 次,WAF 擋 1000 次,ClickHouse 存 1000 筆,平台 5 分鐘內只收到 5 筆。第 5 篇第五節的實測是 ClickHouse 5610 筆對平台 17 筆。

這種設計只有在線外才成立,理由有兩層:

  • 被丟掉的是通知,不是防護。那 995 個請求每一個都已經在線上被 WAF 擋下了。平台少收 995 筆,攻擊者沒有多打進來任何一個請求。
  • 線外的消費者處理量有限。每張案件會啟動一個工作流程,可能還要一個人來看。掃描器一分鐘可以產生幾百筆告警,全部送進平台只會把真正要處理的案件淹掉。封頂保護的是平台與處理案件的人。

線上完全沒有這種餘地。WAF 不能因為忙就跳過幾個請求不檢查,也不能因為忙就丟掉幾個正常請求。如果兩條線是同一條,平台的處理量上限就會變成網站的流量上限。

七、理由六:攻擊面

直接面對攻擊者輸入的程式越少越好。拆成兩條線之後:

  • 線上只有必要的元件在解析攻擊者的請求。平台的案件流程、簽核邏輯、資料庫都不在攻擊者的請求會直接碰到的位置(被保護網站剛好是平台時,碰到的是它當「後端」的那一面,與事件受理端點無關)。
  • 線外的元件改不了請求。攻擊者控制得了紀錄裡的文字(URL、User-Agent 會原樣出現在 audit.log),但線外從 Vector 到平台沒有任何一個元件有管道改寫請求或回應。線外被騙或被攻陷,最壞的結果是假案件或誤封,對應的防線是事件的來源管制與 HMAC 簽章(第 5 篇、第 8 篇)、封鎖保護清單與自鎖保險(第 7 篇)。
  • 兩個方向的連線都由防禦端發起。事件是主機 B 推上去的,決策是主機 B 每 5 秒去拉的。主機 B 不必為了接收決策開任何埠,平台即使被入侵,能交給防禦端的也只有 block、unblock、allow 三種決策紀錄,od-bridge 把它們換成固定的幾個動作(加元素、刪元素、重寫清單檔)。平台沒有辦法對防禦端下任意指令。

八、拆開的代價

以上六個理由都成立,但拆開不是免費的。

代價 說明
封鎖一定比攻擊晚 圖 9-2 的括號那一段。從第一個攻擊請求到來源被封,最快是數秒(全自動流程),慢則到人處理完為止。這段時間的防護完全靠 WAF 逐請求擋
線外只能封來源,不能補擋請求 WAF 規則沒涵蓋到的攻擊請求,已經到了後端。Suricata 可能在 WAF 轉給後端的那一跳看到它並產生告警,平台可以據此建案、封來源,但那個請求已經結束。線外補的是「下一次」,補不了「這一次」
線外看到的是線上願意寫下來的 WAF 的 audit engine 設成 RelevantOnly,只記有規則命中或回應碼 4xx/5xx 的交易(第 2 篇第二節)。沒命中任何規則的請求在 audit.log 裡不存在,線外無從得知
回程的落點受部署限制 線外把決策寫進封鎖清單,但清單要有執行點去讀才有用。對外走 Cloudflare Tunnel 時,訪客的封包不經過主機 B 的實體網卡,nftables blocklist 看不到他們(第 5 篇第二節、第 8 篇第一節)。要讓迴圈對 Internet 上的攻擊者真正閉合,得把 EDL 接到邊界設備(第 6 篇)
兩條線各有一份數字 平台的事件是封頂後的子集。「今天被打幾次」要查 ClickHouse,「有誰在打、處理了沒」才看案件(第 5 篇第五節)。拿案件數當攻擊次數會嚴重低估

第一項代價是可以接受的,前提是 WAF 本身擋得住。兩條線的分工建立在一個假設上:線上的逐請求阻擋是主要防線,線外的封鎖來源是加強。如果 WAF 的規則被關掉、被設成只偵測不阻擋(WAF_RULE_ENGINE=DetectionOnly),封鎖延遲那一段就沒有任何東西在擋。導入初期常用只偵測模式觀察誤判,那段期間要清楚知道自己處在這個狀態。

維運面的代價(多一台主機、要設定保護清單與角色)在第 4 篇第五節,這裡不重複。

九、常見誤解

誤解 實際
封包過了 WAF 之後才到 Suricata、CrowdSec 兩者都不在請求路徑上。Suricata 抄一份實體網卡上的封包,CrowdSec 讀紀錄並當作決策的落地點。請求過了 WAF 之後的下一站是被保護網站
平台慢了或掛了,網站會跟著慢或不通 不會。WAF 只往檔案寫紀錄,不等平台回應。例外是被保護網站剛好就是平台自己,那是因為它是後端(第三節)
平台沒收到的事件,就是沒擋到的攻擊 封頂丟掉的是通知。每一筆都已經被 WAF 當場擋下,ClickHouse 也留有全量
案件還沒簽核之前,攻擊者可以繼續打進來 簽核決定的是要不要封來源。每個攻擊請求仍然各自被 WAF 當場判定,與案件進度無關
封鎖決策落地之後,這個攻擊者的請求就進不來了 要看他從哪條路來、清單由誰在讀。走 Cloudflare Tunnel 的流量 nftables 看不到,仍由 WAF 逐請求擋,除非邊界設備有抓 EDL
接上平台之後 WAF 會多擋一些攻擊 不會。線外不改變線上的規則與門檻(第 4 篇第一節)。平台改變的是擋下之後有沒有人負責、要不要封來源
線外的處置都要等人 流程可以全自動。是否自動只影響線外那一段花多久,不影響它在哪條線上
事件路徑與決策路徑是兩套獨立的機制 是同一個迴圈的去程與回程,兩端都由 od-bridge 負責,連線都由主機 B 發起

上一篇
WAF第 4 篇 設定、調校與驗證
下一篇
驗證清單:怎麼證明每條線在做它該做的事(上)
系列文
一鍵完成六套開源防禦系統整合 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言