Nov 12, 2026

前言

我使用 OWASP Dependency-Track v5.2,把從脆弱性資訊自動收集、Slack 通知,一直到自動登錄到 Pleasanter 的脆弱性應對台帳,花了兩天把整個流程串起來。本文會整理設定步驟,以及實際踩到的坑。

目的,是讓符合 EU《資安韌性法》(CRA)要求的脆弱性應對,能夠不依賴人工手動處理也能持續運作。CRA 要求在知道有被積極利用的脆弱性後 24 小時內發出早期警示。因此,我把「發現」與「期限管理」自動化了。

本文是使用 Pleasanter 的「CRA 報告應對系列」第二回,主題是以 KEV 與 EPSS 為線索,自動偵測「積極利用」。

本文可以做到以下 4 件事:

  1. 每天自動匯入 6 個脆弱性資訊來源(GitHub Advisories、OSV、JVN iPedia、NVD、KEV、EPSS)
  2. 當發現新脆弱性時,立即將 Critical、High、以及已列入 KEV 的項目通知到 Slack
  3. 每天早上 9 點,將新偵測到的項目摘要送到 Slack
  4. 透過 Webhook 與一個小型中繼伺服器,自動在 Pleasanter 的有期限台帳中建立應對紀錄(含應對期限與 CRA 報告期限)

image.png
[嵌入內容:整體架構 · 從資訊來源到 Slack 與 Pleasanter]

Dependency-Track 每天匯入資訊來源,並把新偵測結果送到 Slack 與中繼伺服器;中繼伺服器再透過 Pleasanter API 登錄到台帳中。

環境

我們在 LAN 內使用了 3 台機器。以下 IP 為範例。

角色主機軟體脆弱性管理192.168.0.10Dependency-Track v5.2.0(Docker Compose,服務名稱 apiserver)中繼伺服器192.168.0.20Python 3(僅使用標準函式庫)、systemd台帳192.168.0.30:50001Pleasanter 1.5.9.0通知目標(雲端)Slack 的 Incoming Webhook管理對象的專案是 Pleasanter 1.5.9.1 的 SBOM(CycloneDX 1.7,356 個元件)。

Dependency-Track v5 是以代號 Hyades 重新打造的版本,不能像 v4 那樣直接就地升級。管理介面的結構也和 v4 不同,認證資訊必須先登錄到「密鑰(Secret)」中,再從中選取。

脆弱性資訊來源設定

在管理介面的 管理 → 脆弱性資訊來源 中,依來源逐一啟用。最後設定如下。

來源匯入數量比對鍵備註GitHub Advisories37,742PURL首次鏡像約需 23 分鐘。需要 PATOSV約 27.7 萬PURL必須選擇對應生態系統,否則無法運作JVN iPedia254,000CPE自 1998 年起的資料約 7 分鐘即可匯入NVD約 42.5 萬CPEv5.2 採用 JSON 2.0 feed 方式。沒有 API 金鑰欄位KEV(CISA・ENISA)1,779/39CVE已確認被利用的脆弱性EPSS—CVE 被利用機率預測### GitHub Advisories:PAT 要登錄到「密鑰」

在 v5 中,不能直接把 Token 填進設定畫面。

  1. 在 GitHub 產生 PAT(不需要額外 scope;fine-grained 與 classic 皆可運作)
  2. 在 管理 → 密鑰 中,以 github-pat 這個名稱登錄
  3. 在 脆弱性資訊來源 → GitHub Advisories 中把 Enabled 打開,並在 API Token 下拉選單選擇 github-pat
  4. 先按 Save,再按 Mirror now

有一點要注意:同一畫面中的 GitHub App Private Key 也可以選擇密鑰,但 API Token 與 GitHub App 的認證資訊只能擇一使用。若使用 PAT,請保持空白。

進度可以在 log 中確認。第一次會顯示 updated since null,表示會抓取全部資料。

docker compose logs -f apiserver 2>&1 | grep -iE "github"
# Starting mirror [vulnDataSourceName=github]
# Downloading and processing GitHub advisories updated since null ...
# Completed mirror; processed 37742 vulnerabilities in PT23M22.6S

OSV 與 JVN iPedia

OSV 必須至少選擇一個目標生態系統才會進行鏡像。這次選了 PyPI、NuGet、Maven、Go、npm。若也想判定容器映像中的 OS 套件(Debian、Ubuntu、Alpine),也請一併加入。因為 NVD 沒有依照 OS 發行版保存版本範圍。

JVN iPedia 是 v5 新增的日本國內來源。將 Enabled 打開即可,URL(https://jvndb.jvn.jp/ja/feed)與 Backfill start year(1998)維持預設值就可以。

KEV 與 EPSS:EU 的 KEV 也能標準查看

KEV 支援是 Dependency-Track 5.1.0 的重點功能。可在管理畫面的 KEV Sources 中匯入以下目錄:

  • CISA KEV(美國):預設會鏡像
  • ENISA EU KEV(EU):預設會鏡像
  • VulnCheck KEV:設定免費 API Token 後即可匯入

由於 EU 的 KEV 預設就啟用,因此可以直接說「不只是美國 KEV,也有看 EU KEV」。這在作為 CRA 對應依據時很有幫助。

KEV 欄位與篩選器會顯示在資產組合的脆弱性清單、專案的 Findings、EPSS 檢視,以及稽核檢視中。5.1.0 也新增了可確認與執行 KEV 鏡像狀態的 REST API。

EPSS 也原生支援,REST API 會以 epssScore 取得。筆者環境中,EPSS 每天 10 點更新,KEV 則每天 11 點(JST)更新。

比對鍵差異要注意

NVD 與 JVN 使用 CPE,比對元件;GitHub Advisories 與 OSV 使用 PURL。像 npm 或 NuGet 這類只有 PURL 的 SBOM,NVD 無法偵測到。這次的 10 筆檢出也全部來自 GitHub Advisories。

踩坑:匯入篇

NVD feed 會在中途斷掉

NVD 幾乎每天都會反覆失敗與重試。只看例外訊息,就能知道原因。

docker compose logs apiserver --since 48h 2>&1 \
  | grep -iE "exception|caused by|status code" | grep -v "^\s*at "
# Failed to download feed file from .../nvdcve-2.0-2026.json.gz
# Caused by: java.io.IOException: Received RST_STREAM: Internal error
# GET .../nvdcve-2.0-2003.meta responded with status code 503

這不是設定問題,而是 NVD 在下載過程中把連線切掉了。尤其是數量多的 2025、2026 feed,較容易發生。不過由於是差異式取得,所以失敗當天的資料會在下次成功時一起補進來。筆者環境中,有時第 3 次重試就能完整抓取全年資料。

如果怎麼都不穩定,可以在內部 Web 伺服器中轉 NVD feed,並將 CVE Feeds URL 指向那邊。

來自 JVN 的檢出要懷疑誤判

在匯入 JVN 時,會出現大量以下警告:

WARN [BovModelConverter] BOV for JVNDB-2026-042249 reports CPE
'cpe:2.3:o:google:android:*:...' (version: '*') alongside a diverging
exact version '15.0'; using the CPE's version.

JVN 資料中,有時會同時寫入將版本設為 *(全部版本)的 CPE,以及具體版本。若兩者不一致,Dependency-Track 會採用 *,因此會判定影響範圍更廣。

實際上,[email protected] 與 [email protected] 都曾對應到 2020、2021 年的 JVNDB。只有 JVN 出現的檢出,建議先到 GitHub Advisories 或 NVD 確認受影響版本後再判斷。

可忽略的警告

  • Unrecognized ecosystem ACTIONS:只是因為沒有對應 GitHub Actions 脆弱性的 PURL 型別,所以暫時以 generic 型別處理。
  • Invalid Composer version format 或 MalformedPackageURLException:是將來源資料中的不合法格式逐筆略過所致。

Slack 通知:即時與每日摘要

在 管理 → 通知 → 警示 中建立兩個警示。兩者的發布者都選 Slack,Destination URL 填入 Slack 的 Incoming Webhook URL。

警示觸發群組內容CRA-new-vuln事件NEW_VULNERABILITY、NEW_VULNERABLE_DEPENDENCYCritical・High・已列入 KEV 的項目即時通知日次-新規脆弱性摘要排程(0 0 * * *)NEW_VULNERABILITIES_SUMMARY每天早上 9 點(JST)彙整新檢出並送出警示建立後先儲存,再重新開啟,即可設定群組與送達目標。

使用的通知群組意義如下:

  • NEW_VULNERABILITY:已登錄的元件中出現新的脆弱性時
  • NEW_VULNERABLE_DEPENDENCY:在 SBOM 上傳時,包含已知有脆弱性的元件時
  • VULNERABILITY_RETRACTED:脆弱性資訊被撤回時。這次沒有用到,但看起來很適合在 CRA 報告中將狀態改回「不屬於報告對象」或提醒重新檢討時使用

5.2.0 的通知發布者(publisher)共有 9 種:Console、Email、Jira、Kafka、Mattermost、Microsoft Teams、Slack、Webex、Webhook。

Slack App 的建立方式

Slack 的 Incoming Webhook 需透過建立 Slack App 來發行。

  1. 到 api.slack.com/apps 按 Create New App
  2. 選擇 From scratch → Pick features
  3. 權限只保留 incoming-webhook,不訂閱事件。按 Create and Install,並選擇通知頻道(例如:#cra-alerts)
  4. 把產生出的 Webhook URL 貼到警示的 Destination URL

截至 2026 年 11 月,App 建立畫面已改成「From a template/From scratch(Paste a manifest/Pick features)/With a coding agent」的結構。請注意這和舊教學文章的畫面不同。

另外,系統還會自動建立一個不需要的 App-Level Token(connections:write)。這個不需要,請刪除。Webhook URL 只要知道就能對該頻道發文,因此不要把它拍進截圖;若外洩,就刪除 Webhook 再重建。

即時通知的篩選器要依群組分流

v5 的警示具有以 CEL 撰寫的 Filter Expression。一開始我放入以下式子:

subject.vulnerability.severity in ["CRITICAL", "HIGH"] || subject.vulnerability.is_kev

但這樣在 NEW_VULNERABLE_DEPENDENCY 的通知中會出現下列錯誤,因為這個群組的通知並沒有 subject.vulnerability。

WARN [NotificationRouter] Failed to evaluate filter expression for rule CRA-new-vuln; Failing open
field 'vulnerability' is not declared in message '...NewVulnerableDependencySubject'

因為是「Failing open」,所以通知不會中斷,而是全部送出,但篩選器等於沒作用。應改成依群組分流的寫法:

group == Group.GROUP_NEW_VULNERABLE_DEPENDENCY
  || subject.vulnerability.severity in ["CRITICAL", "HIGH"]
  || subject.vulnerability.is_kev

把已列入 KEV 當作條件之一,就能直接用於 CRA 中「被積極利用的脆弱性」判斷。

每日摘要一定要設定「限定」

排程型警示如果不在 限定 中指定至少一個專案,就不會運作。Cron 以 UTC 指定,因此 0 0 * * * 代表日本時間每天早上 9 點。

若開啟「如果沒有變更,則跳過公開」,那麼沒有新檢出時就不會送出。隔天早上若沒有收到通知,可從 log 確認是被跳過了。

INFO [ProcessScheduledNotificationRuleActivity] No new findings since rule was last processed
     [notificationRuleName=日次-新規脆弱性摘要]

另外要注意,如果限定中只指定一個有版本號的專案,當你把新版本另建成另一個專案時,就會變成不在範圍內。建議改用標籤限定,或採用父子結構並啟用「包含啟用中的子專案」,即可省去這種麻煩。

Pleasanter 整合:Webhook 與中繼伺服器

我們用一個小型中繼伺服器接收 Dependency-Track 的 Webhook,並透過 Pleasanter API 建立紀錄。Pleasanter API 需要在本文中放入 API Key 與自訂格式 JSON,因此無法直接從 Dependency-Track 傳送。加入中繼伺服器後,可以做到以下 3 件事:

  • 驗證簽章,防止冒充
  • 不重複建立相同脆弱性,也不覆寫人工填入的欄位
  • 依深刻度與是否列入 KEV 計算應對期限與 CRA 報告期限

Webhook 傳來的 JSON

v5 的 Webhook 所送出的 JSON,官方文件沒有提供具體範例。因此我先架了一個只負責接收並保存的伺服器,確認測試通知的內容(節錄如下)。

{
  "notification": {
    "group": "GROUP_NEW_VULNERABILITY",
    "timestamp": "2026-11-12T11:55:29.352Z",
    "title": "[TEST] New Vulnerability Identified on Project: ...",
    "subject": {
      "component": { "uuid": "...", "name": "componentName", "version": "componentVersion" },
      "project":   { "uuid": "...", "name": "projectName", "version": "projectVersion", "tags": ["tag1"] },
      "vulnerability": {
        "vulnId": "INT-001", "source": "INTERNAL",
        "aliases": [{ "vulnId": "OSV-001", "source": "OSV" }],
        "description": "...", "recommendation": "...",
        "cvssv3": 6.6, "cvssv4": 7.7, "severity": "MEDIUM",
        "cwes": [{ "cweId": 666, "name": "..." }],
        "isKev": false
      }
    }
  }
}

這個通知中沒有 EPSS。另外,沒有分數的脆弱性會以 cvssv4 = 0 傳來,因此需要當成未填寫處理。

台帳以「有期限台帳」建立

脆弱性應對是一項必須在期限內完成的工作。適合使用可標示開始、完成、狀態、負責人的有期限台帳,並能強調逾期資料。欄位配置如下:

欄位顯示名稱填入值標題標題GHSA-846f-f764-76mx [email protected] 的格式開始開始偵測時間(JST)完成完成應對期限:Critical 7 天、High 14 天、Medium 30 天、Low 90 天狀態狀態未著手(100)ClassA唯一鍵脆弱性ID|專案 UUID|元件 UUID(唯讀)ClassB、ClassC、ClassH脆弱性ID、來源、別名讓 GHSA 與 CVE 的對應一眼可見ClassD深刻度CRITICAL、HIGH 等ClassE、ClassF元件、專案PURL、專案名稱與版本ClassGCWECWE-407 等NumA、NumBCVSS v3、CVSS v4 分數CheckAKEV是否列入 KEVDateBCRA 報告期限若列入 KEV,則為偵測後 24 小時內(參考值)內容內容描述、建議處理方式、Dependency-Track 連結完成與 CRA 報告期限都以「年月日時分」格式顯示。因為 24 小時的期限如果看不到時間,實務上就無法管理。

中繼伺服器的運作方式

這是一支只用 Python 標準函式庫撰寫、約 270 行的腳本(dtrack_relay.py)。重點有 3 點。

1. 驗證簽章:v5.2 的 Webhook 在設定 Signing Secret 後,會附帶 X-Webhook-Signature: sha256=<HMAC-SHA256> 送出。必須在解析本文前先驗證。

def signature_ok(body, header):
    if not header or not header.startswith("sha256="):
        return False
    expected = hmac.new(SECRET.encode("utf-8"), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header[7:].strip().lower())

2. 不重複登錄:使用唯一鍵查找既有紀錄,若存在就只更新脆弱性資訊。狀態、開始、完成、負責人只在新增時才填入,因此不會覆蓋人工進行中的處理。之所以不使用 Pleasanter 的 upsert API,是因為它無法區分這個行為。

def find_existing(key):
    res = pl_post(f"{PL_SITE}/get", {"View": {
        "ColumnFilterHash": {"ClassA": key},
        "ColumnFilterSearchTypes": {"ClassA": "ExactMatch"},
    }})
    rows = (res.get("Response") or {}).get("Data") or []
    rows = [r for r in rows if (r.get("ClassHash") or {}).get("ClassA") == key]
    return rows[0] if rows else None

def push_to_pleasanter(n):
    key, data, initial = to_record(n)
    row = find_existing(key)
    if row:
        rid = row.get("IssueId")
        pl_post(f"{rid}/update", data)          # 只更新脆弱性資訊
        return f"更新 #{rid}"
    rec = {**initial, **data}                    # 只有新增時才填入狀態與期限
    rec["ClassHash"] = {"ClassA": key, **data["ClassHash"]}
    res = pl_post(f"{PL_SITE}/create", rec)
    return f'新增 #{res.get("Id")}'

如果同一脆弱性再次通知,而那時已列入 KEV,只有在 CRA 報告期限尚未填寫時,才會填入從當下起算 24 小時後的時間。Pleasanter 中未填寫的日期會以 1899-12-30 返還,因此要把它視為空值。不過如後所述,Dependency-Track 不會因為 KEV 新增而發出通知,所以單靠這個處理,無法完全捕捉「後來才進入 KEV」的案例。

CRA 報告期限只是參考值。列入 KEV 並不等於法律上已經「知悉」。前提是由人員在確認自家產品是否受影響、以及是否確實被利用後,手動輸入知悉時間,再據此計算正式期限。

3. 即使失敗也不漏接:收到的 JSON 會先存到 payloads/,並立即對 Dependency-Track 回傳 200。即使 Pleasanter 登錄失敗,也可以之後重新送出。

python3 dtrack_relay.py --replay payloads/20261112_205529_496754_GROUP_NEW_VULNERABILITY.json
# 重送 ...: INT-001 MEDIUM → 新增 #815

Dependency-Track 端設定

  1. 在 管理 → 密鑰 中,將用 openssl rand -hex 32 產生的字串以 webhook-signing 登錄
  2. 建立警示:名稱 Pleasanter 連攜,發布者 Webhook,觸發器為事件
  3. 儲存後再打開,將群組設為 NEW_VULNERABILITY、Destination URL 設為 http://192.168.0.20:8090/dtrack、Signing Secret 設為 webhook-signing,並在限定中設定目標專案

Destination URL 要指定在容器可達的 LAN IP。因為從容器內看 localhost 代表的是容器本身。

用 systemd 常駐執行

設定寫在 .env 中,並用 chmod 600 讓其他人無法讀取。systemd 的 EnvironmentFile 可以直接讀取 KEY='值' 這種寫法。執行使用者(dtrack)與部署路徑(/opt/dtrack-relay)僅為範例。

# /opt/dtrack-relay/.env
DT_WEBHOOK_SECRET='...'
PLEASANTER_URL=http://192.168.0.30:50001
PLEASANTER_SITE_ID=123
PLEASANTER_API_KEY='...'
DT_FRONTEND_URL=http://192.168.0.10:8081
# /etc/systemd/system/dtrack-relay.service
[Unit]
Description=Dependency-Track to Pleasanter relay
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=dtrack
WorkingDirectory=/opt/dtrack-relay
EnvironmentFile=/opt/dtrack-relay/.env
ExecStart=/usr/bin/python3 -u /opt/dtrack-relay/dtrack_relay.py
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/dtrack-relay/payloads

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now dtrack-relay
journalctl -u dtrack-relay -f

動作確認與踩坑:通知篇

比起設定本身,我花最多時間的是「到底什麼時候會通知」。

同一個 BOM 重新上傳也不會通知

NEW_VULNERABILITY 只適用於新出現的檢出。即使把同一份 BOM 再放回已經檢出過的專案,或重新分析,也不會再通知。要測試真正的通知,必須將同一份 BOM 上傳到新的測試專案。因為對新專案來說,全部都是新的檢出。

「限定」要在通知發出前先設定

在測試專案上試的時候,Slack 收到了通知,但中繼伺服器卻完全沒收到。把 log 的時間排在一起後,就能看出原因。

時間(JST)事件21:41:58將 BOM 上傳到 relay-test 1,觸發 10 筆通知21:42Slack 收到通知(未設定限定的警示)21:48:14將 relay-test 1 加入 Pleasanter 連攜的限定並登錄21:53:27在 relay-test 2 先設定限定,再上傳→ 10 筆全部登錄在通知產生時不在限定內的專案,即使之後再加入也不會送出。正確順序是:「建立專案 → 加入警示限定並登錄 → 上傳 BOM」。此外,如果刪除專案再重建,UUID 會改變,也會從限定中脫落。

為了方便這類切分,建議把警示的 發行成功 log 打開。

後來列入 KEV 也不會通知

這是最大的坑。5.2.0 沒有專屬於 KEV 的通知群組。已經檢出的脆弱性,之後即使被列入 KEV,也不會再觸發通知。

CRA 的 24 小時早期警示,正是針對這種「原本已知的脆弱性,後來開始被利用」的情境。也就是說,只靠 Webhook 會漏掉「積極利用」的偵測。解法是定期透過 Dependency-Track 的 REST API 檢查 KEV 與 EPSS,並反映到台帳。KEV 資訊不必自行取得,直接使用 Dependency-Track 鏡像後的資料即可。

從公開到偵測的延遲能否控制在 24 小時內

資訊來源每天同步一次,另外還會加上資產組合重新分析的間隔,所以從公開到偵測本來就會有延遲。如果像 NVD 那樣某天抓取失敗,也會再多拖延。CRA 的 24 小時是從「知悉」開始算,因此偵測延遲不等於直接縮短期限。不過,實際延遲有多少還是應該掌握,目前還會持續驗證。

總結與後續

透過 Dependency-Track v5.2 的標準功能,加上只使用標準函式庫的中繼伺服器,已經能把「脆弱性資訊收集 → 通知 → 有期限的應對台帳」整個流程自動化。對於載入與 Pleasanter 1.5.9.1 相同 SBOM 的測試專案,High 6 筆、Medium 4 筆的檢出,已分別以附帶期限的紀錄形式登錄到台帳中。

目前仍待處理的課題如下:

  • 定期抓取後來列入 KEV 與 EPSS 變化的工作:從 Dependency-Track 的 REST API 取得 KEV 狀態與 epssScore,更新台帳中的 KEV/EPSS。用來偵測 Webhook 無法捕捉的「積極利用」
  • 自動建立 CRA 報告台帳:根據收到的專案 UUID 對應產品,將 CRA 報告以「判定中」建立。知悉時間由人員在判定時輸入
  • 驗證偵測延遲:結合同步間隔與重新分析間隔,測量從公開到偵測所需時間
  • 既有檢出的初次匯入:Webhook 只會送出新檢出,因此在正式運作前的檢出需透過 REST API 批次匯入
  • 稽核結果與 VEX:把台帳中判定為「無影響」的項目回寫到 Dependency-Track 的稽核或 VEX。也可一併納入撤回(VULNERABILITY_RETRACTED)通知
  • 監控對象的標籤管理:讓每次版本升級時,不必重新替換警示的限定條件

這些內容將作為使用 Pleasanter 的「CRA 報告應對系列」持續整理。

參考


原文出處:https://qiita.com/yyoshida15/items/4ea6a0752ff2ba942dff


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
boluoji
📝35   💬3   ❤️6
813
🥈
我愛JS
📝3   💬1   ❤️3
63
🥉
站長阿川
📝1   💬3   ❤️2
42
#4
mike
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登