iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
自我挑戰組

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

驗證清單:怎麼證明每條線在做它該做的事(下)

  • 分享至 

  • xImage
  •  

五、線外回程:六項

回程要證明的是:不該封的封不了、該封的在幾秒內真的落到 kernel 與清單、到期之後真的消失。前面的 TEST- 案件就是這一節的起點。

# 要證明的事 性質 通過的樣子
R1 保護清單會擋下不該封的目標 反證 攻擊者是內網位址的案件,封鎖決策寫不進去:決策列表沒有那個位址的 block,主機 B 的 blocklist 也沒有
R2 決策被拉走 正證 按下「封鎖攻擊來源」之後 5 秒內,od-bridge 的 log 出現 decision … action=block target=ip/203.0.113.42;平台上該筆由 pending 變 applied
R3 交接點寫的一方(nftables):kernel 裡真的有這個元素 正證 nft get element 印出元素與剩餘時間,結束碼 0
R4 od-bridge 看到的與 kernel 一致 正證 /state/nft 的 blocklist 有同一個位址與剩餘秒數;allowlist 是 ADMIN_IPS 加平台主機
R5 交接點寫的一方(EDL):清單檔有這一行 正證+反證 /edl 回 200、多一行 203.0.113.42。封鎖前先看一次:空清單也要回 200 而不是 404
R6 到期或撤銷之後真的消失 反證 nft get element 結束碼 1;/edl 回到空;平台上原決策是 expired 或 revoked,並多一筆 unblock

R5 的反證要在封鎖之前先看:

#### 主機 B:封鎖前
sudo nft get element inet secstack blocklist '{ 203.0.113.42 }'; echo rc=$?     # rc=1
curl -s -D - http://127.0.0.1:8500/edl                                          # HTTP 200,內容為空

然後用資安人員 linda.hu 打開 TEST- 案件,填處置意見後按「封鎖攻擊來源」,回主機 B:

#### 主機 B:封鎖後
cd /opt/integrated-waf
sudo docker compose logs --tail 30 od-bridge | grep 'decision '                  # R2
sudo nft get element inet secstack blocklist '{ 203.0.113.42 }'; echo rc=$?     # R3:rc=0
curl -s http://127.0.0.1:8500/state/nft                                         # R4
curl -s http://127.0.0.1:8500/edl                                               # R5
odstat                                                                          # processed 與 applied 各加一
table inet secstack {
	set blocklist {
		type ipv4_addr
		flags interval,timeout
		elements = { 203.0.113.42 timeout 1h expires 59m41s212ms }
	}
}
rc=0
{"blocklist": {"203.0.113.42": 3581}, "blocklist6": {}, "allowlist": ["192.168.0.111", "192.168.0.50"]}
203.0.113.42

平台這一側用示範企業管理員看「開放防禦 → 決策列表」(這一頁只有企業管理員看得到):

  • 「決策歷史」分頁:該筆狀態 applied。
  • 「生效中名單」分頁的對帳欄:平台會回頭向 od-bridge 要 /edl 與 /state/nft,逐筆比對。顯示「同步」表示平台的紀錄與主機 B 的實際狀態一致;「缺漏」是平台認為生效、主機 B 上卻沒有。這一欄要平台的 .env 有設 OD_BRIDGE_URL 才有內容,沒設時頁面會提示「無法連線到防禦節點,以下只是平台端記錄」。

R6 二選一:等 TTL 到期(標準流程是 1 小時),或由企業管理員在決策列表撤銷。之後把「封鎖前」那兩行指令再打一次,結果要回到 rc=1 與空清單。/edl 最晚會比 kernel 多留 60 秒,那是 od-bridge 清理過期項目的週期。

R1 的做法是送一筆「攻擊者是內網位址、目標是外部主機名稱」的合成事件(目標不是內網才過得了去程的過濾),讓它建案後照常處置:

#### 主機 B
inject TEST-PROT 192.168.0.77
#### 到平台處置「驗證事件 TEST-PROT」這張案件之後:
sudo nft get element inet secstack blocklist '{ 192.168.0.77 }'; echo rc=$?     # 仍是 rc=1

出廠的保護清單涵蓋 RFC1918 等 16 個網段(第 5 篇第三節),任何流程要寫封鎖決策之前都會比對,命中就不寫。案件在畫面上怎麼收尾依流程而定(出廠流程會先判斷有沒有公網來源,沒有就轉給資安人員人工確認),但不管怎麼處置,決策列表都不會出現 192.168.0.77 的 block,主機 B 也不會有這個元素。這一項與 R3 是一對:203.0.113.42 封得了、192.168.0.77 封不了,兩個都成立才能說保護清單在運作。

R3 證明的是「清單裡有」,不是「攻擊者被擋了」。203.0.113.42 是文件保留位址,不會真的有封包從它來。要看 kernel 真的丟包,得封一台自己控制、又不在 allowlist 裡的機器(內網機器要先在保護清單加豁免,第 5 篇第三節),然後從那台連主機 B 的 22 或 8500 埠,結果應該是逾時。更重要的是效力範圍:走 Cloudflare Tunnel 的訪客不經過主機 B 的實體網卡,blocklist 對他們沒有作用(第 5 篇第二節、第 8 篇第一節)。回程六項全過,代表「決策會落地」,不代表「Internet 上的攻擊者被封了」;後者要看 EDL 有沒有邊界設備在抓。

六、對帳:哪些數字該相等,哪些不該

前面三節是逐點驗證。另一種驗法是拿同一段時間的幾個數字互相對:有些應該相等,有些應該是大於,而且差距有固定的來源。差距的來源對不上,就是有東西壞了。

# 左邊 關係 右邊 差距的正當來源
1 audit.log 的行數 = ClickHouse 的 WAF 事件數 每小時整點輪替的那幾秒可能少幾行(第 5 篇第七節)。持續少很多是 Vector 沒在讀
2 ClickHouse 的事件數 ≥ od-bridge 的 total 增量 過濾(內網對內網、Suricata 資訊級)與三道封頂。左邊遠大於右邊是正常的;右邊長期是 0 而左邊有公網來源的事件,才是問題
3 od-bridge 的 2xx 增量 = 平台 od_intake_events 的新增筆數 重複的 correlation_id 平台回 200 但不新增。4xx、5xx、unreachable 有任何成長都要追
4 平台的事件數 ≥ 新案件數 同攻擊者同規則 60 分鐘內併案(第 3 篇第三節)
5 平台上生效中、執行點含 nftables 的 block 決策 = kernel blocklist 的元素;/edl 的行數(執行點含 edl 者) 同一目標重複封鎖是多筆決策對一個元素;主機 B 重開機或有人手動重建表之後左邊會多於右邊,直到 TTL 到期(第 8 篇第四節)。平台的「對帳」欄做的就是這一列

第 3、4 列在主機 A 查,第 2 列在主機 B 查:

#### 主機 A:最近 24 小時平台收到幾筆事件、分屬幾張案件
sudo -u postgres psql -d beakplatform <<'SQL'
SELECT count(*) AS events, count(DISTINCT case_secure_code) AS cases
FROM od_intake_events
WHERE received_at > (now() AT TIME ZONE 'UTC') - interval '24 hours';
SQL

#### 主機 B:最近 24 小時 ClickHouse 裡攻擊者是公網位址的事件數(這些才有機會送平台)
sudo docker compose exec -T clickhouse clickhouse-client --user secstack --password "$CH_PW" --query \
  "SELECT source_system, count() FROM secstack.events
   WHERE event_time > now() - INTERVAL 24 HOUR
     AND NOT (startsWith(IPv6NumToString(actor_ip),'::ffff:192.168.') OR startsWith(IPv6NumToString(actor_ip),'::ffff:10.') OR startsWith(IPv6NumToString(actor_ip),'::ffff:172.'))
   GROUP BY source_system"

平台的時間欄位存的是不帶時區的 UTC,所以條件要寫 now() AT TIME ZONE 'UTC';直接拿 now() 去減,在非 UTC 時區的主機上會差出整個時區的小時數。

七、一個實例:三組證據互不代替

這是一台實際運作中的防禦節點上發生過的事,位址已去識別化。

現象。節點連續運作七天。所有容器都是 Up,有健康檢查的都是 healthy;SQL injection 探測回 403;決策輪詢每 5 秒一次、每次都回 200。看起來一切正常。但 odstat 是這樣的:

… Intake (Vector → bridge → BP) 25670 total 21 2xx 0 4xx 25649 5xx 0 unreachable …

兩萬五千多次轉送,成功的只有 21 次。翻 od-bridge 的 log,同一個 correlation_id 反覆出現:

INFO ingest: forwarded cid=3bf3afd5-… src=suricata status=500
INFO ingest: forwarded cid=3bf3afd5-… src=suricata status=500
INFO ingest: forwarded cid=3bf3afd5-… src=suricata status=500

原因是兩件事疊在一起。平台對某一筆事件固定回 500;od-bridge 把 5xx 轉成 502 回給 Vector,而 Vector 的 http sink 對 5xx 會重送同一筆,直到成功為止。這一筆永遠不會成功,於是它佔住了送往平台的那一路,排在它後面的事件一筆都送不出去。從那一刻起的四天半,線外去程是停的。

當時看得到的證據 結果 為什麼沒有幫助
容器狀態 全部正常 沒有任何行程掛掉。Vector、od-bridge、平台都活著,只是在重複同一件失敗的事
SQL injection 探測 403 線上的證據。線上與線外之間沒有呼叫關係,線外停了,線上不會知道
ClickHouse 事件持續寫入 全量那一路與送平台那一路是分開的,一路卡住不影響另一路
決策輪詢 每 5 秒回 200 回程的證據。去程與回程是 od-bridge 裡兩個獨立的迴圈
平台的案件處置中心 很安靜 「沒有新案件」與「沒有人在打」長得一模一樣

抓到它的是第四節 O4 的那一行,以及第六節對帳表的第 2、3 列:這段期間 ClickHouse 有來自公網位址的 Suricata 事件,平台卻一筆都沒收到,5xx 則一直在長。這就是「分線驗」的意思。線上的六項、回程的六項在這個狀態下全部通過,只有去程的檢查會失敗。

去程的健康訊號就這三個,都在 odstat 那一行裡。5xx 與 unreachable 兩次查看之間沒有成長;4xx 沒有成長(401 是開通字串失效,422 是沒有事件路由,403 是金鑰的來源系統範圍不含這個來源);od-bridge 的 log 裡沒有同一個 cid 連續出現。回程的訊號是另外兩個:last executor iter 在 10 秒以內,current token valid for 大於 0。

八、不能互相代替的證據

你看到的 它證明 它不證明
容器全部 Up/healthy 行程活著、健康端點有回應 任何一條線在做它該做的事
--verify 通過 線上在擋(L3)、元件活著、連得到平台 事件有送達平台、決策會落地。它不送任何事件
SQL injection 回 403 線上 線外任何一段;也不證明正常請求過得去(要配 L2)
--test-event 建出案件 去程從 Vector 注入口到平台建案這一段 WAF 在擋、Vector 有在讀 audit.log。合成事件從 8688 進去,不經過 WAF 也不經過檔案,所以它要配 O1
ClickHouse 有事件 Vector 讀到並解析了 平台收到了。那是另一路
平台沒有新案件 沒有事件通過過濾與封頂,或者去程壞了 沒有被攻擊。要分辨是哪一種,看 ClickHouse 與 odstat
決策狀態 applied od-bridge 當時回報執行成功 kernel 現在還有那個元素(重開機、重建表之後就沒有);也不證明攻擊者被擋了(看效力範圍)
blocklist 是空的 目前沒有生效中的封鎖 od-bridge 壞了。TTL 到期、重開機、流程只選了 EDL 執行點都會是空的
/edl 有那個位址 清單檔寫好了 有設備在抓、抓了有套用。安裝包內沒有任何元件讀 EDL(第 6 篇)
處置中心的案件數 有幾個「攻擊者 × 手法」被送上來 被打了幾次。次數要問 ClickHouse(第 5 篇第五節)

九、什麼時候驗、驗哪幾項

時機 至少跑這樣
剛裝完 十八項全部跑一次。順序照本篇:線上、去程、回程
改了主機 B 的 .env 並 --reconfigure L1 到 L3(來源管制與 WAF 還在)、R4(allowlist 是新的名單、生效中的封鎖有補回來)、odstat
改了 WAF 覆寫檔、升級 WAF 映像 L2 到 L6。新版 CRS 可能多出規則,L2 與 L5 這兩個反證最容易在這時候失敗
改了平台的事件路由、流程、保護清單 O4、O5、R1 到 R3
重發開通字串、平台重裝或換位址 O4(事件受理金鑰)、R2(執行帳號)。兩把憑證是分開的,要各驗一次
主機 B 重開機 L1 到 L3、R4。blocklist 會歸零,這是預期(第 8 篇第六節),對帳表第 5 列會暫時不相等
覺得平台太安靜 第六節對帳表的第 2、3 列

日常只要三行,各對一條線:

#### 1. 線上(工作機):預期 403
curl -s -o /dev/null -w '%{http_code}\n' "http://192.168.0.112:8080/beakplatform/?id=1%27%20OR%201=1--"

#### 2. 線外的兩個迴圈(主機 B):5xx、unreachable 沒有成長;last executor iter 在 10 秒內;token 大於 0
odstat

#### 3. 去程有沒有貨(主機 A):ClickHouse 有公網來源事件的日子,這裡不該是 0
sudo -u postgres psql -d beakplatform -Atc "SELECT count(*) FROM od_intake_events WHERE received_at > (now() AT TIME ZONE 'UTC') - interval '24 hours'"

三行分別回答三個問題:線上還在擋嗎、線外的去程與回程還在轉嗎、轉的時候有沒有東西真的送到。任何一行不對,回到對應那一節逐項查;三行都對,就是第 9 篇那張圖上的兩條線各自在做它該做的事。


上一篇
驗證清單:怎麼證明每條線在做它該做的事(上)
下一篇
Suricata:站在網卡旁邊的第二雙眼睛
系列文
一鍵完成六套開源防禦系統整合 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言