昨天做完欄位自我審查。今天走一次分析管線——但只走前半段,規則式、不需要 LLM 的那一半。 後半段(AI 怎麼把這些東西講成一句話)留給明天跟 Day 26 分開處理,今天先把地基打好。
設計上,一筆告警要變成人看得懂的分析,大致走這幾步:解析欄位並對映實體 → 關聯聚合 → 分級 → MITRE 對應 → 產出。
這幾步裡,前四步其實不需要 AI——它們是純規則式的查表跟比對:欄位存不存在就是存不存在,規則有沒有在關聯清單裡就是有沒有,MITRE 編號有沒有對應卡片就是有沒有。 真正需要 AI 的,只有最後一步:把這些查表結果組織成一段人看得懂的敘事。
我想把這兩半分開處理是有原因的:如果我今天直接讓 AI 從頭做到尾,沒辦法區分「這個結論是查表查出來的」還是「AI 自己判斷出來的」。 分開之後,前半段的正確性可以直接用程式驗證(有沒有查到就是有沒有查到),後半段的可信度才是真正需要另外測試的東西——這部分我留到 Day 26。
用 Day 19 那筆真實資料當輸入
我寫了一支小腳本 build_case.py,把「欄位 → 實體」「關聯檢查」「分級因子檢查」「MITRE 查表」這四步串起來。輸入用的是 Day 19 那筆 sudo 失敗告警——只填入 dashboard 當時真的顯示過的欄位,其餘一律留空,不補值:
{
"rule": {
"level": 10,
"description": "Three failed attempts to run sudo",
"mitre": { "id": ["T1548.003"] }
},
"agent": { "name": "manager-host" },
"data": { "win": {} }
}
跑完,實際輸出長這樣(節錄):
{
"entities": {
"host": "manager-host",
"host_ip": null,
"account": null,
"source_ip": null,
"workstation": null,
"_note": "any null above means the field genuinely wasn't in the input, not that extraction failed silently"
},
"correlation": {
"in_known_correlation_table": false,
"note": "此 rule_id 不在本專案已實作的關聯規則清單內(或未提供 rule_id)。這不代表沒有關聯,只代表本 KB 目前沒有為它設計對應的關聯邏輯。"
},
"severity": {
"rule_level": 10,
"five_tier_mapping": "undefined — 門檻尚未定案,此處不臆造",
"escalation_factors_checked": {
"repetition_observed": {
"description": "同一描述在短時間內出現 2 次",
"determinable_from_input": true
}
}
},
"mitre": {
"mitre_lookup": [{
"id": "T1548.003",
"has_technique_card": false,
"fallback": "T1548",
"note": "本 KB 只有 T1548 的廣泛技術卡,沒有子技術 T1548.003 的專屬卡片"
}]
}
}
一個真實跑出來的缺口:子技術沒有對應卡片
這個輸出裡最有意思的一段是 MITRE 那部分。 這筆告警帶的技術編號是 T1548.003(濫用 sudo 提權),但我的 14 張技術卡裡只有廣泛的 T1548,沒有這個子技術的專屬卡片。 腳本老實回報「沒有對應卡片,退回廣泛卡片,細節建議查 MITRE 官方確認」,而不是假裝有對到。
這正好是 Day 18 那句話的一個真實案例:MITRE 對應的完整度取決於我的規則集跟卡片有多少,不是取決於現實世界的技術有多細。 之前那句話是我推論出來的,今天這筆輸出是第一次真的用程式跑出一個具體例子。
其他欄位(帳號、來源 IP、工作站)全部是 null,原因很單純:這是 Linux 的 sudo 事件,不是 Windows 的 data.win.eventdata. 事件,那些欄位本來就不會存在。 腳本沒有因為欄位是空的就試著猜一個值填進去,只是老實回報「這個欄位真的不存在」——這個區分我覺得很重要,寫程式的時候特別留了一句註解提醒自己不要在這裡偷懶...
一個意外的小插曲:我自己也踩了編碼坑
跑這支腳本的時候,第一次執行,終端機印出來的中文全部變成亂碼。 查了一下,是 Windows 主控台預設編碼跟程式輸出的 UTF-8 對不上——這正是我 wiki 裡 powershell-51-encoding-traps.md 記錄過的同一類問題。 加上 PYTHONUTF8=1 這個環境變數才印對。
這件事本身沒有很嚴重,但我覺得值得記一筆:連我自己寫一支十分鐘的示範腳本,都會踩到自己之前記錄過的坑。 這某種程度上證明了那份坑清單是真的有用的——不是寫完就沒人看,是真的會在意外的地方冒出來提醒你。
明天
接下來想講知識庫怎麼設計—— 一份文件怎麼被切段、怎麼被找到、一個問題怎麼被路由到候選頁。 這篇我打算直接拿真實的 index.md 跟 routing 規則示範一次。
明天見。