iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0

昨天我們成功透過 Webhook,讓 Wazuh Server 能夠在觸發告警時,主動對我們的 FastAPI 接收站發送 HTTP POST 請求。

看著終端機印出 200 OK 固然令人興奮,但目前的程式碼只是單純印出一句「收到資料」,我們其實根本不知道到底發生了什麼事。Wazuh 傳遞過來的資料是一大包 JSON 格式的 Payload,裡面包含了時間、主機名稱、觸發規則、甚至詳細的 CVE 漏洞資訊。

今天,我們就要來實作「資料解析(Data Parsing)」,就像是在海量的帳本中精準抓出我們要的稽核數字一樣,我們要把這包 JSON 拆開,萃取出真正有價值的資安情報。


Wazuh Alert JSON 結構解析

在寫程式之前,我們必須先了解 Wazuh 傳過來的 JSON 長什麼樣子。Wazuh 的告警結構是一層包一層的巢狀字典(Nested Dictionary),以下是擷取了重要欄位的結構範例:

{
  "timestamp": "2026-09-11T15:00:00.000+0800",
  "rule": {
    "level": 7,
    "description": "Vulnerability severity: High",
    "id": "23505"
  },
  "agent": {
    "id": "001",
    "name": "LAPTOP-3007V8UH",
    "ip": "192.168.1.100"
  },
  "data": {
    "vulnerability": {
      "cve": "CVE-2023-12345",
      "severity": "High",
      "package": {
        "name": "python3",
        "version": "3.10.6"
      }
    }
  }
}

可以觀察到,我們最關心的資訊分散在三個主要區塊中:

  1. rule:包含告警等級與規則描述。
  2. agent:受害主機的身分與 IP。
  3. data.vulnerability:專屬於漏洞偵測模組的資料,包含 CVE 編號與出問題的套件名稱。

改寫 FastAPI 路由:實作資料萃取

了解結構後,我們回到 main.py,把原本那個只會印出「叮咚」的路由重新改寫。

在處理這類巢狀 JSON 時,最忌諱直接使用中括號 payload["rule"]["level"] 來取值。因為資安告警的種類非常多,並不是每一次的告警都一定包含 vulnerability 欄位(例如有人密碼輸入錯誤,就不會有 CVE 資料)。如果直接存取不存在的 Key,程式就會直接拋出 KeyError 導致伺服器崩潰。

專業的寫法:善用 .get() 方法
在 Python 的字典操作中,使用 .get("key", default_value) 可以在找不到該鍵值時回傳預設值,確保程式的強健性(Robustness)。

請將 main.py 內的路由改寫為以下程式碼:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
import logging
import uvicorn

# ==========================================
# 1. 初始化日誌系統 (取代傳統 print)
# ==========================================
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)-7s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger("Wazuh-Webhook")

# ==========================================
# 2. 定義 Pydantic 資料模型 (Data Models)
# ==========================================
# 預先定義 Wazuh 傳來的 JSON 結構,FastAPI 會自動進行反序列化與型別驗證
class RuleModel(BaseModel):
    level: int = Field(default=0, description="告警等級")
    description: str = Field(default="No description", description="告警描述")

class AgentModel(BaseModel):
    name: str = Field(default="Unknown", description="主機名稱")
    ip: Optional[str] = Field(default=None, description="主機IP")

class WazuhPayload(BaseModel):
    rule: RuleModel
    agent: AgentModel
    # 針對動態欄位 (如 vulnerability),保留彈性的 Dict 結構
    data: Optional[Dict[str, Any]] = None

# ==========================================
# 3. 建立 FastAPI 實體與路由
# ==========================================
app = FastAPI(title="SecOps Backend API", version="1.0.0")

@app.post("/webhook/wazuh")
async def receive_wazuh_alert(payload: WazuhPayload):
    """
    接收 Wazuh 主動推送的 Webhook 告警資料
    """
    try:
        # 1. 輸出基礎告警日誌 (直接透過 payload.物件屬性 存取)
        logger.info(f"[告警接收] 主機: {payload.agent.name} | 等級: {payload.rule.level} | 描述: {payload.rule.description}")
        
        # 2. 針對「漏洞偵測 (Vulnerability)」進行進階解析
        if payload.data and "vulnerability" in payload.data:
            vuln = payload.data["vulnerability"]
            cve_id = vuln.get("cve", "Unknown CVE")
            severity = vuln.get("severity", "Unknown")
            pkg_name = vuln.get("package", {}).get("name", "Unknown Package")
            
            # 偵測到漏洞,改用 WARNING 等級輸出
            logger.warning(f"🚨 [漏洞情報] {cve_id} | 受害套件: {pkg_name} (嚴重度: {severity})")

        return {"status": "success", "message": "Alert processed"}

    except Exception as e:
        # 捕捉未預期錯誤,並回傳 500 狀態碼
        logger.error(f"解析失敗: {str(e)}")
        raise HTTPException(status_code=500, detail="Internal Server Error")

if __name__ == "__main__":
    # 使用字串 "main:app" 取代直接傳入 app,這也是生產環境的標準作法
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

驗證解析結果

修改完成後,在終端機按下 Ctrl + C 終止伺服器,再重新執行 python main.py 啟動。

只要我們靜靜等待 Wazuh 發送下一波告警,你就會看到原本單調的終端機,現在會輸出結構化、極度清晰的威脅情報:

2026-09-14 15:30:22 | INFO    | [SECURITY_EVENT] Agent: LAPTOP-3007V8UH | Rule_Level: 7 | Desc: Vulnerability severity: High
2026-09-14 15:30:22 | WARNING | [CVE_DETECTION] CVE_ID: CVE-2023-38325 | Package: curl | Severity: High

這種格式排除了多餘的視覺干擾,明確切分出時間戳記、日誌等級與事件核心數據。到這裡,我們的 FastAPI 已經正式進化為一個具備強大解析能力的「情報過濾中心」。

目前我們已經成功將資安情報收斂至 Python 的變數中。下一步,我們不能只讓這些資訊留在終端機裡。回想我們在專案初期建立的 n8n 資料夾,明天我們即將引入強大的開源自動化工具 n8n,把這些淬鍊好的資安情報,自動推播到我們的通訊軟體上!


上一篇
Day 08 : 實作主動式威脅告警-設定 Wazuh Webhook 與 FastAPI 介接
下一篇
Day 10 : 實作系統解耦-使用 Docker 部署 n8n 開源自動化工作流
系列文
從漏洞告警到 AI 決策:Wazuh × RAG × n8n 實作自動化資安漏洞驗證與智慧通報 共 11 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言