項目|說明
本篇一句話目標|拿著 01 或 02 篇取得的 API Key,從你的設備(或先用工作機)呼叫 POST /api/trigger/form,建出第一張資安案件並在處置中心看到它。
你需要的身分|一把授權範圍含「資安案件」分類(或個別資安表單)的 API Key;驗證畫面時需要能開「開放防禦 / 資安案件處置中心」的帳號(例:資安人員 linda.hu@demo-soc.example)。
做完會得到什麼|一張案件編號形如 PROC-20260926-0002 的資安案件、一份你的設備欄位到表單 28 個欄位的對照、一段可直接放進設備或排程的呼叫指令。
預估時間|15 至 30 分鐘(不含把設備接上的部分)。
前兩篇拿到的是一組 key_id 與 secret。這一篇把它用起來:由設備端對平台送一個帶簽章的 HTTP 請求,平台就會依你指定的表單建立一張資安案件並啟動它的處置流程,值班人員在「開放防禦 / 資安案件處置中心」看到案件、按決策按鈕。
全篇用同一個固定情境,範例指令與回應都照它寫:
| 項目 | 值 |
|---|---|
| 偵測設備 | WAF-01(192.168.0.111,讀者的防禦節點) |
| 攻擊來源 | 203.0.113.42 |
| 被攻擊目標 | www.demo.internal 的 /login |
| 攻擊型態 | SQL injection(UNION SELECT,10 秒內 3 次) |
| 命中規則 | WAF-SQLI-001(規則集 owasp-crs) |
| 嚴重度 | 4 |
| 平台 | http://192.168.0.112:8000/beakplatform |
| 企業/表單 | DemoSOC,表單 SEC_INCIDENT_RESPONSE(資安事件處置) |
指令一律用三個環境變數帶認證,之後每一段都沿用:
export BP_BASE_URL='http://192.168.0.112:8000/beakplatform'
export BP_KEY_ID='ak_a85efc52cc2455a4'
export BP_SECRET='<領取時顯示一次的 secret>'
secret 原樣貼上,不要自己轉碼
畫面顯示的 secret 是 base64url 字串。做 HMAC 時用的是它解碼後的 raw bytes,bp_trigger.py 會自己解碼;第 6 節的純 curl 寫法也有解碼那一行。先自行轉成十六進位或再 base64 一次,結果一律是 401 auth_failed。
平台有兩個能用 API Key 建資安案件的入口。本系列只講第一條;第二條是給整合型防禦節點用的,欄位格式與授權方式都不同,這裡只列差別讓你確認自己沒走錯門。
| 比較項目 | POST /api/trigger/form(本系列) | POST /api/open_defense/intake |
|---|---|---|
| 適合誰 | 任何設備、SIEM、維運腳本;只要能算 HMAC 就能送 | 會送 OCSF 事件(或已在「事件路由設定」建好來源格式的原生 payload,走 /api/open_defense/intake/native?profile=…)的整合型防禦節點 |
| Key 的授權範圍 | 「授權範圍 - 表單分類」或「授權範圍 - 個別表單」(02 篇核發的 Key 則是表單模板) | 「授權範圍 - 資安事件接收(OpenDefense intake,選填)」,並列出允許的 source_system |
| Body 的欄位 | 就是表單欄位:form_code、subject、form_data{…};key 只能是表單裡存在的欄位 | OCSF 結構(severity_id 必須是 0 到 6 的整數、correlation_id 等),由平台轉成表單欄位 |
| 建到哪張表單 | 呼叫端在 form_code 指定;「事件路由設定」的規則不會改送到別張表單,只拿來讀該表單的「聚合」設定 | 由「事件路由設定」依優先序比對規則決定目標表單;沒有命中就 422 no_mapping |
| 重送同一事件 | 依聚合設定併入既有案件(回 200 merged:true,見 04 篇) | correlation_id 是冪等鍵,同 id 重送回 duplicate:true;另外也套聚合 |
| 成功回應 | 201 帶案件編號 execution_code 與表單序號 serial_number | 200 帶 case_secure_code |
簽章與驗證順序
兩條完全相同:headers X-BP-Key-Id/X-BP-Timestamp/X-BP-Signature,驗證順序 headers → 時間戳 ±300 秒 → Key 狀態 → 來源 IP 白名單 → 簽章;失敗一律 401 auth_failed
判斷方式很簡單:你的設備能不能自己組出「表單欄位名 = 值」這種 JSON?能,就走 /api/trigger/form。它不需要任何路由規則就能建案,設備欄位怎麼對到表單欄位由你在設備端決定(第 4 節)。
建單工具 <平台安裝目錄>/scripts/bp_trigger.py 是單一檔案、只用 Python 3 標準函式庫,可以直接複製到任何有 python3 的主機。先用 --list 確認這把 Key 看得到哪些表單:
python3 bp_trigger.py --list
它打的是 GET /api/trigger/forms,只回這把 Key 授權範圍內、而且目前有「已發行」版本的表單。DemoSOC 的 Key 勾了「資安案件」分類時,輸出長這樣(field_keys 為節省篇幅只列前幾個):
列出可發動表單成功(HTTP 200)
{
"data": [
{
"code": "SEC_INCIDENT_RESPONSE",
"field_keys": ["actor_asn", "actor_country", "actor_ip", "actor_user_agent", "actor_xff", "confidence", "..."],
"form_code": "SEC_INCIDENT_RESPONSE",
"is_security": true,
"name": "資安事件處置",
"published_secure_code": "<已發行版本的識別碼>",
"version": "AA"
},
{ "code": "SEC_IR_SOC_TEAM", "name": "資安事件處置(SOC 團隊版)", "is_security": true, "...": "..." },
{ "code": "SEC_IR_SOLO", "name": "資安事件處置(小企業單人版)", "is_security": true, "...": "..." }
],
"success": true
}
| 輸出欄位 | 意義 |
|---|---|
| form_code(與 code 相同) | 建單時 --form-code 要填的穩定代號;表單重新發行也不會變 |
| name | 表單在畫面上的名稱 |
| field_keys | 這張表單接受的全部欄位 key(依字母排序);送了不在清單裡的 key 會被 400 unknown_field 擋下 |
| is_security | true 表示是資安案件表單:案件會進處置中心、而且會套用事件聚合 |
| published_secure_code、version | 目前生效的已發行版本;一般不需要用到(body 也可以用 published_secure_code 取代 form_code,但重新發行後就失效,只適合一次性測試) |
DemoSOC 出廠有三張資安表單,28 個欄位完全相同,差別只在綁的處置流程:
| form_code | 名稱 | 流程差別 |
|---|---|---|
| SEC_INCIDENT_RESPONSE | 資安事件處置 | 最小版:Start → 資安人員簽核 → End,決策按鈕「封鎖攻擊來源」「放行(可接受風險)」「誤判結案」 |
| SEC_IR_SOC_TEAM | 資安事件處置(SOC 團隊版) | 供 3~8 人輪班監控中心:簽核與 SLA 計時雙軌,逾時催辦、再逾時升級通報資安主管 |
| SEC_IR_SOLO | 資安事件處置(小企業單人版) | 供單一資安人員:非上班時段高危案件自動封鎖後留待複核,上班時段交人工、逾時仍自動封鎖 |
本篇一律用 SEC_INCIDENT_RESPONSE。要換成另外兩張,只需改 --form-code,欄位對照表不變。
這是本篇的核心。三張出廠資安表單的 28 個欄位分成五組(畫面上是五個折疊面板,標題與下表的組名相同)。「畫面標籤」是案件在處置中心與表單裡顯示的欄位名,「你的設備通常對到什麼」是建議的對應方式,依你的設備調整。
六條共同軸線
表中標「軸線」的六個欄位——severity_id、actor_ip、target_host、source_system、finding_rule_id、occurred_at——是平台判斷事件是否併入既有案件、計算風險分數、顯示案件摘要時讀的欄位。其中 actor_ip 與 finding_rule_id 是預設的分組鍵:兩個都有值,同來源同規則的後續事件才會併進同一張案件;任一缺值就各自開案(細節見 03 篇)。其他四條軸線可在「事件路由設定」的聚合區塊勾為分組鍵。
| 欄位 key | 畫面標籤 | 型別 | 你的設備通常對到什麼 | 影響併案 |
|---|---|---|---|---|
| finding_title | 事件標題 | 文字 | 告警的一句話標題(例 SQL injection attempt on /login)。處置中心「持續事件」分頁每筆事件都會顯示它,建議一定帶 | 否 |
| finding_summary | 事件摘要 | 多行文字 | 告警說明、命中的 payload 片段、次數等給值班人員看的脈絡 | 否 |
| event_class | 事件類別 | 文字 | 你自己的事件分類(例 web_attack);trigger 路徑不會拿它做路由,純顯示 | 否 |
| source_system | 偵測來源 | 文字 | 設備識別名(例 waf-01)。處置中心「案件摘要」的「偵測來源」讀它 | 軸線;預設不是分組鍵 |
| severity_id | 嚴重度 (OCSF 1-5) | 數字 | 依 OCSF 嚴重度自行對映你的等級(例 WAF 的 critical→5、high→4、medium→3、low→2)。畫面標籤寫 1-5;平台的 OCSF 入口接受 0~6,這條路徑不檢查範圍,建議用 1~5 | 軸線;決定案件嚴重度(併入時只升不降)與是否可併入已結案案件 |
| confidence | 信心度 | 數字 | 偵測端的信心值(例 0~100);純顯示 | 否 |
| occurred_at | 發生時間 | 文字 | 設備記錄的事件時間,建議 ISO 8601 UTC(例 2026-09-26T14:02:11Z)。缺值時「首次出現」「最近出現」改用平台收到的時間 | 軸線;預設不是分組鍵 |
| correlation_id | 事件關聯 ID | 文字 | 你這端的告警 ID,方便回查;在這條路徑不是冪等鍵(重送靠聚合而不是靠它) | 否 |
| 欄位 key | 畫面標籤 | 型別 | 你的設備通常對到什麼 | 影響併案 |
|---|---|---|---|---|
| actor_ip | 攻擊者 IP | 文字 | WAF/防火牆看到的 client IP。設備在反向代理後面時要放真實來源 IP,不是代理 IP | 軸線;預設分組鍵。也是風險分數(24 小時內同源事件數、該 IP 歷史封鎖次數)與封鎖決策的對象 |
| actor_asn | ASN | 文字 | 來源 ASN(設備有查才填) | 否 |
| actor_country | 國別 | 文字 | 來源國別碼(例 GeoIP 的兩碼) | 否 |
| actor_user_agent | User-Agent | 文字 | HTTP 請求的 User-Agent 原文(例 sqlmap/1.8) | 否 |
| actor_xff | X-Forwarded-For 原文 | 文字 | 請求的 X-Forwarded-For 整串原文,留給人工判斷代理鏈 | 否 |
| 欄位 key | 畫面標籤 | 型別 | 你的設備通常對到什麼 | 影響併案 |
|---|---|---|---|---|
| target_host | 目標主機 | 文字 | 被攻擊的主機名或 IP(例 www.demo.internal);WAF 通常對到請求的 Host | 軸線;預設不是分組鍵 |
| target_url | 目標 URL | 文字 | 請求路徑(例 /login) | 否 |
| target_service | 目標服務 | 文字 | 服務或協定名(例 https、ssh) | 否 |
| 欄位 key | 畫面標籤 | 型別 | 你的設備通常對到什麼 | 影響併案 |
|---|---|---|---|---|
| finding_rule_id | 規則 ID | 文字 | 命中的規則識別碼(例 WAF-SQLI-001、Suricata 的 SID)。處置中心「案件摘要」的「規則 ID」讀它 | 軸線;預設分組鍵 |
| finding_rule_set | 規則集 | 文字 | 規則所屬的規則集(例 owasp-crs) | 否 |
| detector_hint_action | 偵測端建議動作 | 文字 | 設備自己的建議(例 block);只是給值班人員參考,平台不會照它自動執行 | 否 |
| detector_hint_ttl_sec | 建議 TTL (秒) | 數字 | 設備建議的封鎖時長(例 3600);同上,僅供參考 | 否 |
這一組送件時不要填
下列 8 個欄位由平台在建案與併案時寫入。它們出現在 field_keys 裡,技術上送了不會被 400 擋下,但建案時會被平台算出的值覆蓋,或者留下誤導值。
| 欄位 key | 畫面標籤 | 型別 | 平台怎麼填 | 影響併案 |
|---|---|---|---|---|
| risk_score | 風險分數 (0-100) | 數字 | 嚴重度 ×15 + 24 小時內同源事件數 ×5(最多算 6 次)+ 該 IP 歷史封鎖次數 ×10(最多算 3 次),上限 100 | 否(併入時重算) |
| recommended_action | 建議處置 | 文字 | 風險分數 ≥ 60 為 block,否則 observe | 否 |
| od_event_count | 本案件聚合事件數 | 數字 | 建案為 1,每併入一筆 +1;就是回應裡的 od_event_count | 否 |
| od_repeat_count | 24 小時內同源事件數 | 數字 | 建案時查過去 24 小時內、經事件接收入口(/api/open_defense/intake)進案且 actor_ip 相同的事件數;只走本篇路徑的環境沒有那類事件,這個值通常是 0 | 否 |
| od_history_block_count | 該 IP 歷史封鎖次數 | 數字 | 建案時查同一 actor_ip 的封鎖決策筆數 | 否 |
| intel_summary | 情報摘要 | 多行文字 | 保留給情報彙整用;送件端不要填 | 否 |
| od_first_seen | 首次出現 | 文字 | 建案時取 occurred_at,沒有就用收到的時間 | 否 |
| od_last_seen | 最近出現 | 文字 | 建案同上;每併入一筆事件就更新 | 否 |
三張出廠表單都沒有設定任何必填欄位,所以只帶一個欄位也能建案。但少了 actor_ip 或 finding_rule_id,之後同一波攻擊的每筆告警都會各自開一張單,第 9 節的「最小欄位集」就是為了避免這件事。
照第 1 節的情境,把 WAF-01 這筆告警送進去。--subject 是案件在處置中心清單上顯示的標題,必填;每個 --field 是「欄位 key=值」:
python3 bp_trigger.py --form-code SEC_INCIDENT_RESPONSE \
--subject 'WAF-01 SQL injection 203.0.113.42 -> www.demo.internal' \
--field finding_title='SQL injection attempt on /login' \
--field finding_summary='WAF 攔截到 /login 的 POST 參數含 UNION SELECT,10 秒內 3 次' \
--field event_class='web_attack' \
--field source_system='waf-01' \
--field severity_id=4 \
--field confidence=90 \
--field occurred_at='2026-09-26T14:02:11Z' \
--field actor_ip=203.0.113.42 \
--field actor_country=XX \
--field actor_user_agent='sqlmap/1.8' \
--field target_host=www.demo.internal \
--field target_url='/login' \
--field target_service=https \
--field finding_rule_id=WAF-SQLI-001 \
--field finding_rule_set=owasp-crs \
--field detector_hint_action=block \
--field detector_hint_ttl_sec=3600
成功時工具印出「建單成功(HTTP 201)」與平台回應:
建單成功(HTTP 201)
{
"data": {
"execution_code": "PROC-20260926-0002",
"form_instance_secure_code": "<表單實例識別碼>",
"serial_number": "Form-260900021",
"workflow_instance_secure_code": "<流程實例識別碼>"
},
"message": "表單已送出,流程已啟動",
"success": true
}
| 回應欄位 | 意義 |
|---|---|
| execution_code | 案件編號,處置中心清單與案件標題旁顯示的就是它(PROC- 加日期加流水號)。跟值班人員溝通時報這個 |
| serial_number | 表單引擎配的表單序號(Form- 開頭)。資安案件不在一般「表單中心」列出,所以畫面上通常看不到它;保留在你的設備日誌裡供對帳即可 |
| workflow_instance_secure_code | 流程實例的識別碼。處置中心的案件 API 以它定位案件 |
| form_instance_secure_code | 表單實例的識別碼,與上一個一對一 |
同一事件再送一次時不會多開一張單:只要 actor_ip 與 finding_rule_id 相同、且在聚合視窗內,平台回 200 並帶 merged: true,工具會印「事件已併入既有案件 PROC-20260926-0002(累計 2 筆)」。這是 04 篇的主題。
設備端已經組好 JSON 時,用 --json 一次帶入整包 form_data;仍可用 --field 覆蓋或追加單一欄位(--field 優先):
python3 bp_trigger.py --form-code SEC_INCIDENT_RESPONSE \
--subject 'WAF-01 SQL injection 203.0.113.42 -> www.demo.internal' \
--json '{"finding_title":"SQL injection attempt on /login","source_system":"waf-01","severity_id":4,
"occurred_at":"2026-09-26T14:02:11Z","actor_ip":"203.0.113.42","target_host":"www.demo.internal",
"target_url":"/login","finding_rule_id":"WAF-SQLI-001"}' \
--field finding_rule_set=owasp-crs
--field 送出的值一律是字串(severity_id=4 送出去是 "4"),平台照樣接受;要送真正的數字用 --json。
| 參數 | 說明 |
|---|---|
| --base/--key-id/--secret | 分別對應環境變數 BP_BASE_URL/BP_KEY_ID/BP_SECRET;命令列給了就優先。三者缺任一個立刻退出,不會連平台 |
| --list | 列出可建的表單(第 3 節) |
| --form-code CODE | 建案;必須同時給 --subject |
| --field KEY=VALUE | 表單欄位,可重複;沒有 = 會被當參數錯誤 |
| --json '{…}' | 整包 form_data,必須是 JSON 物件 |
| --group-key TEXT | 送件端自訂的案件分組鍵(case_group_key),04 篇說明 |
| --selftest CODE | 對指定表單跑四項驗證:列表 200、未知欄位 400、不存在表單 404、錯誤簽章 401。它只驗連線與簽章,不驗你的欄位填得完不完整 |
| 退出碼 | 意義 | 你的排程或設備該怎麼處理 |
|---|---|---|
| 0 | 成功(含 merged: true 的 200) | 不重試 |
| 1 | 沒帶任何參數,只印了使用說明 | 修呼叫方式 |
| 2 | 參數錯:缺認證、缺 --subject、--field 格式錯、secret 不是合法 base64url | 修參數,不重試 |
| 3 | 平台回非 2xx(第 8 節的錯誤碼) | 依錯誤碼處理;4xx 不要盲目重試 |
| 4 | 連不上平台(DNS、逾時、連線被拒) | 退避後重試 |
設備或 SIEM 上沒有 Python 時,自己組請求。簽章規格如下:
| Header | 值 |
|---|---|
| X-BP-Key-Id | key_id(ak_ 開頭) |
| X-BP-Timestamp | 送出當下的 Unix 秒;與平台時鐘相差超過 300 秒即 401 |
| X-BP-Signature | sha256= 加上 hex(HMAC-SHA256(secret_raw, "{timestamp}\n{body}"))。secret_raw 是 secret 字串 base64url 解碼後的 raw bytes;被簽的字串是「時間戳、一個換行、原始 body」,換行只有一個、body 後面沒有換行 |
| Content-Type | application/json |
下面這段 bash 與「個人設定 / 我的 API Key」的「查看串接範例」視窗產生的格式相同,用第 5 節的欄位改寫;需要 openssl、base64、xxd、curl:
BASE='http://192.168.0.112:8000/beakplatform'
KEY_ID='ak_a85efc52cc2455a4'
SECRET="$BP_SECRET"
BODY='{"form_code":"SEC_INCIDENT_RESPONSE","subject":"WAF-01 SQL injection 203.0.113.42 -> www.demo.internal","form_data":{"finding_title":"SQL injection attempt on /login","source_system":"waf-01","severity_id":4,"occurred_at":"2026-09-26T14:02:11Z","actor_ip":"203.0.113.42","target_host":"www.demo.internal","target_url":"/login","finding_rule_id":"WAF-SQLI-001","finding_rule_set":"owasp-crs"}}'
TS=$(date +%s)
KEY_HEX=$(printf '%s' "$SECRET" | tr '_-' '/+' | base64 -d | xxd -p | tr -d '\n')
SIG=$(printf '%s\n%s' "$TS" "$BODY" | openssl dgst -sha256 -mac HMAC -macopt hexkey:"$KEY_HEX" -hex | awk '{print $NF}')
curl -s -X POST "$BASE/api/trigger/form" \
-H 'Content-Type: application/json' \
-H "X-BP-Key-Id: $KEY_ID" \
-H "X-BP-Timestamp: $TS" \
-H "X-BP-Signature: sha256=$SIG" \
-d "$BODY"
逐行說明:
在其他語言實作時
只有三件事:(1) secret 用 base64url 解碼成 bytes;(2) 先把 body 序列化成最終要送的 bytes,再用同一份 bytes 算 HMAC;(3) 時間戳用 Unix 秒的字串。bp_trigger.py 用的序列化是 json.dumps(body, ensure_ascii=False, separators=(',', ':')),但這不是要求,任何序列化都可以,只要簽的與送的是同一份。
用能開處置中心的帳號登入(DemoSOC 出廠的 SEC_INCIDENT_RESPONSE 流程指派給角色「資安人員」,linda.hu@demo-soc.example 持有它),開「開放防禦 / 資安案件處置中心」:
僅我可簽核」預設是開著的
清單頂端的「僅我可簽核」按鈕預設啟用,只列輪到你這一關簽核的案件。上方統計「進行中案件」有數字、清單卻空的,通常是你的帳號沒有這張表單流程簽核節點要求的角色(預設流程是「資安人員」),不是建單失敗。按掉「僅我可簽核」可以看到全部案件;要能按決策按鈕仍然要有角色。
兩件容易誤判的事:
平台的錯誤回應是 JSON,主要看 error;驗章失敗的回應只有 {"error": "auth_failed"},其餘還多一個 success: false。工具遇到非 2xx 印「建單失敗(HTTP …)」並以退出碼 3 結束。
| HTTP | error | 最常見原因 |
|---|---|---|
| 400 | unknown_field | form_data 裡有表單沒有的 key(例把 actor_ip 寫成 src_ip)。回應的 details.unknown_keys 列出打錯的 key,details.allowed_keys 列出全部 28 個合法 key,照它改 |
| 400 | missing_required_fields | 表單設計者在表單上設了必填欄位而你沒帶或帶空值;details.missing_keys 列出缺的 key。出廠三張資安表單沒有必填欄位,自訂表單才會遇到 |
| 400 | missing_subject | body 沒有 subject 或是空字串。用工具時是忘了 --subject(工具會先擋下,退出碼 2) |
| 400 | missing_form_identifier | body 既沒有 form_code 也沒有 published_secure_code |
| 400 | form_data_must_be_object | form_data 不是 JSON 物件(例送成陣列或字串) |
| 400 | invalid_json | body 不是合法 JSON;純 curl 時常是引號被 shell 吃掉 |
| 400 | invalid_case_group_key | case_group_key 不是字串或超過 128 字元 |
| 401 | auth_failed | 五種原因共用同一個回應,平台刻意不區分:缺 header、時間戳與平台相差超過 300 秒、Key 已暫停/撤銷/過期或 key_id 打錯、來源 IP 不在白名單、簽章不符(secret 沒解碼、body 簽完又改)。依這個順序逐項排除 |
| 403 | scope_denied | 用 published_secure_code 指定表單、而那張表單不在 Key 的授權範圍。用 form_code 時同樣情況回的是 404 form_not_found |
| 404 | form_not_found | form_code 不存在,或存在但不在這把 Key 的授權範圍(平台不區分,避免探測)。先跑 --list 看 Key 看得到哪些表單 |
| 422 | form_not_published | 表單在授權範圍內但目前沒有「已發行」版本(message 會分「表單尚未發行」與「有發行記錄但目前沒有 Published 版本」)。請表單設計者發行 |
| 422 | applicant_invalid | Key 綁定的專用系統帳號已停用或刪除。請管理員在「API Key 管理」的「編輯」改綁或解除綁定 |
| 422 | workflow_error | 表單建好但流程啟動失敗,message 有原因;通常是流程配對或發行狀態的問題,找表單設計者 |
| 429 | (限流訊息) | 同一把 Key 超過每分鐘 100 次或每小時 5000 次;或同一來源 IP 連續 401 超過每分鐘 30 次。退避後重試,後者先修好認證 |
| 500 | internal_error | 平台內部錯誤。退避後重試,並通知平台管理員 |
三個實際回應範例:
# 欄位打錯名(src_ip)
建單失敗(HTTP 400)
{"details": {"allowed_keys": ["actor_asn", "actor_country", "actor_ip", "..."], "unknown_keys": ["src_ip"]},
"error": "unknown_field", "success": false}
# secret 錯
列出可發動表單失敗(HTTP 401)
{"error": "auth_failed"}
# 表單不在授權範圍(用了人資表單的 form_code)
建單失敗(HTTP 404)
{"error": "form_not_found", "success": false}
先決定「最小欄位集」。不論用哪種接法,每筆事件至少帶這七個欄位,之後的併案、風險分數、案件摘要才會正確:
| 欄位 | 為什麼一定要 |
|---|---|
| finding_title | 清單與「持續事件」分頁的每筆事件都顯示它;沒有就只剩 subject |
| source_system | 案件摘要的「偵測來源」;多台設備接進來時靠它分辨 |
| severity_id | 決定案件嚴重度、SLA、風險分數、是否可併入已結案案件 |
| actor_ip | 預設分組鍵之一;也是封鎖決策的對象與歷史封鎖查詢的依據 |
| target_host | 案件摘要與事件摘要顯示;可勾為分組鍵 |
| finding_rule_id | 預設分組鍵之一;缺它每筆告警都會各自開單 |
| occurred_at | 「首次出現」「最近出現」的依據;缺值會用平台收到的時間,和設備的時間對不上 |
actor_ip 與 finding_rule_id 為什麼非帶不可,03 篇有實際跑出來的對照(帶了併成一單、缺了各自開單)。
條件是設備的 webhook 功能要能自己算 HMAC-SHA256 並放進 header——多數設備的「HTTP 通知」只能填固定 URL 與固定 header,算不出隨時間變動的 X-BP-Signature,這種就不能直接接。能寫腳本的設備(例如有 Lua、Python 或 shell hook 的 WAF)照第 6 節的三個步驟實作:解碼 secret、用送出的 body bytes 算簽章、帶三個 header。secret 放在設備的密碼庫或只有 root 可讀的設定檔,不要寫在通知規則的畫面裡。
最常見的情況。在設備旁或能讀到 log 的主機放一支輪詢腳本,做三件事:讀新增的告警行、把欄位對到第 4 節的 key、呼叫 bp_trigger.py。範例(以每筆告警已整理成一行 JSON 為前提):
#!/bin/bash
# 由排程每分鐘執行;環境變數 BP_BASE_URL / BP_KEY_ID / BP_SECRET 放在 root 0600 的檔案裡 source 進來
set -u
. /etc/waf-01/bp.env
LOG=/var/log/waf-01/alerts.jsonl
OFFSET_FILE=/var/lib/waf-01/bp.offset
done_lines=$(cat "$OFFSET_FILE" 2>/dev/null || echo 0) # 上一輪已處理的行數
n=$done_lines
while read -r line; do
python3 /usr/local/bin/bp_trigger.py --form-code SEC_INCIDENT_RESPONSE \
--subject "WAF-01 $(echo "$line" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d["rule"],d["client_ip"])')" \
--json "$(echo "$line" | python3 -c '
import sys, json
d = json.load(sys.stdin)
print(json.dumps({
"finding_title": d["msg"], "source_system": "waf-01", "severity_id": d["sev"],
"occurred_at": d["ts"], "actor_ip": d["client_ip"], "target_host": d["host"],
"target_url": d["uri"], "finding_rule_id": d["rule"], "finding_rule_set": d.get("ruleset", ""),
}))')"
rc=$?
if [ "$rc" -eq 4 ]; then break; fi # 連不上平台:停在這一行,offset 不推進,下一輪從這裡重送
if [ "$rc" -eq 3 ]; then echo "line $((n + 1)) rejected rc=3" >&2; fi # 4xx:內容或授權問題,記錄後跳過
n=$((n + 1))
done < <(tail -n +"$((done_lines + 1))" "$LOG")
echo "$n" > "$OFFSET_FILE"
要點:退出碼 4(連不上)不推進讀取位置,下一輪重送;退出碼 3 的 4xx 是內容或授權問題,重送也不會過,記錄下來人工處理。重送同一筆事件不會多開單,所以偶爾重複送是安全的。
多數 SIEM/SOAR 的 HTTP 動作允許在送出前跑一段腳本或表達式。把第 6 節的三步驟放進那段腳本(大部分平台內建 HMAC-SHA256 函式),body 用 SIEM 的告警欄位對到第 4 節的 key。留意兩點:SIEM 常會替你「美化」JSON——要確定簽章算的是最終送出的那份;以及 SIEM 主機的時鐘要 NTP 同步,否則整批 401。一個 SIEM 規則可能同時涵蓋多台設備,這時 source_system 填原始設備名,不要填 SIEM 名,案件摘要才看得出來源。
時鐘差超過 5 分鐘,整台設備的請求全部 401
症狀:昨天還好好的,今天每一筆都 auth_failed,secret 沒改過。
原因:平台只接受與自己相差 300 秒內的 X-BP-Timestamp,設備時鐘飄掉就整批失敗,而且回應不會告訴你是時間問題。
正確做法:設備與平台都做 NTP 同步;排除 401 時先比對 date +%s 與平台主機的差值。
secret 自行轉碼或被引號吃掉,恆 401
症狀:用 --selftest 第 1 項就 401。
原因:secret 要原樣交給工具(它自己解 base64url);純 curl 時 SECRET 用了單引號包 $BP_SECRET,或把畫面上的 secret 又 base64 一次、轉成 hex。
正確做法:複製畫面上的字串原樣放進 BP_SECRET;純 curl 照第 6 節那段,不要改 KEY_HEX 那行。
主旨缺少 → 400 missing_subject;欄位打錯名 → 400 unknown_field
這兩個是接新設備時最先遇到的。前者是 body 沒有 subject(工具會在本機就擋下);後者照回應裡的 allowed_keys 改欄位名。欄位名區分大小寫,且不能用畫面標籤(「攻擊者 IP」)代替 key(actor_ip)。
表單分類勾了,建案卻 404 form_not_found
Key 的授權範圍是對的,但那張表單目前沒有「已發行」版本時,平台回的是 422 form_not_published;若 form_code 根本不在授權範圍才是 404。先跑 --list:列得出來的表單一定建得了;列不出來,不是 Key 沒授權那個分類,就是表單沒發行或不在該分類下。
同一事件重送不會多開單
只要 actor_ip 與 finding_rule_id 相同且在聚合視窗內,第二筆會回 200 merged:true 併進第一張案件,「聚合事件數」+1。這是設計,不是漏建;設備端重試機制可以放心重送。什麼情況會併、什麼情況不會,見 04 篇。
「事件路由設定」的規則不會把 trigger 事件改送到別張表單
DemoSOC 出廠有一條「高嚴重度走 SOC 團隊版」規則,但它只對 /api/open_defense/intake 路徑生效。你在 form_code 指定哪張表單,案件就建在哪張;規則只拿來讀該表單的聚合設定。要讓 WAF 的高嚴重度事件走 SOC 團隊版,設備端自己依嚴重度改 --form-code。
「情報彙整」那組欄位不要送
risk_score、recommended_action、od_event_count 等 8 個欄位由平台寫;送了不會報錯,但會被覆蓋或留下誤導值(第 4.5 節)。
第一張案件建好之後,設備會持續送第二筆、第三筆。哪些會併進同一張案件、哪些會開新單、怎麼用 case_group_key 把一次掃描的多個 IP 併成一單、在案件上看到什麼,見 04 持續事件如何累加到同一張案件。
還沒有 API Key 的讀者回 02 企業管理員直接配發 API Key 或 03 透過表單中心申請 API Key。