Nov 12, 2026
我使用 OWASP Dependency-Track v5.2,把從脆弱性資訊自動收集、Slack 通知,一直到自動登錄到 Pleasanter 的脆弱性應對台帳,花了兩天把整個流程串起來。本文會整理設定步驟,以及實際踩到的坑。
目的,是讓符合 EU《資安韌性法》(CRA)要求的脆弱性應對,能夠不依賴人工手動處理也能持續運作。CRA 要求在知道有被積極利用的脆弱性後 24 小時內發出早期警示。因此,我把「發現」與「期限管理」自動化了。
本文是使用 Pleasanter 的「CRA 報告應對系列」第二回,主題是以 KEV 與 EPSS 為線索,自動偵測「積極利用」。
本文可以做到以下 4 件事:

[嵌入內容:整體架構 · 從資訊來源到 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 填進設定畫面。
github-pat 這個名稱登錄github-pat有一點要注意:同一畫面中的 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 必須至少選擇一個目標生態系統才會進行鏡像。這次選了 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 支援是 Dependency-Track 5.1.0 的重點功能。可在管理畫面的 KEV Sources 中匯入以下目錄:
由於 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 幾乎每天都會反覆失敗與重試。只看例外訊息,就能知道原因。
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 時,會出現大量以下警告:
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,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 的 Incoming Webhook 需透過建立 Slack App 來發行。
incoming-webhook,不訂閱事件。按 Create and Install,並選擇通知頻道(例如:#cra-alerts)截至 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=日次-新規脆弱性摘要]
另外要注意,如果限定中只指定一個有版本號的專案,當你把新版本另建成另一個專案時,就會變成不在範圍內。建議改用標籤限定,或採用父子結構並啟用「包含啟用中的子專案」,即可省去這種麻煩。
我們用一個小型中繼伺服器接收 Dependency-Track 的 Webhook,並透過 Pleasanter API 建立紀錄。Pleasanter API 需要在本文中放入 API Key 與自訂格式 JSON,因此無法直接從 Dependency-Track 傳送。加入中繼伺服器後,可以做到以下 3 件事:
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
openssl rand -hex 32 產生的字串以 webhook-signing 登錄Pleasanter 連攜,發布者 Webhook,觸發器為事件NEW_VULNERABILITY、Destination URL 設為 http://192.168.0.20:8090/dtrack、Signing Secret 設為 webhook-signing,並在限定中設定目標專案Destination URL 要指定在容器可達的 LAN IP。因為從容器內看 localhost 代表的是容器本身。
設定寫在 .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
比起設定本身,我花最多時間的是「到底什麼時候會通知」。
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 打開。
這是最大的坑。5.2.0 沒有專屬於 KEV 的通知群組。已經檢出的脆弱性,之後即使被列入 KEV,也不會再觸發通知。
CRA 的 24 小時早期警示,正是針對這種「原本已知的脆弱性,後來開始被利用」的情境。也就是說,只靠 Webhook 會漏掉「積極利用」的偵測。解法是定期透過 Dependency-Track 的 REST API 檢查 KEV 與 EPSS,並反映到台帳。KEV 資訊不必自行取得,直接使用 Dependency-Track 鏡像後的資料即可。
資訊來源每天同步一次,另外還會加上資產組合重新分析的間隔,所以從公開到偵測本來就會有延遲。如果像 NVD 那樣某天抓取失敗,也會再多拖延。CRA 的 24 小時是從「知悉」開始算,因此偵測延遲不等於直接縮短期限。不過,實際延遲有多少還是應該掌握,目前還會持續驗證。
透過 Dependency-Track v5.2 的標準功能,加上只使用標準函式庫的中繼伺服器,已經能把「脆弱性資訊收集 → 通知 → 有期限的應對台帳」整個流程自動化。對於載入與 Pleasanter 1.5.9.1 相同 SBOM 的測試專案,High 6 筆、Medium 4 筆的檢出,已分別以附帶期限的紀錄形式登錄到台帳中。
目前仍待處理的課題如下:
epssScore,更新台帳中的 KEV/EPSS。用來偵測 Webhook 無法捕捉的「積極利用」VULNERABILITY_RETRACTED)通知這些內容將作為使用 Pleasanter 的「CRA 報告應對系列」持續整理。
原文出處:https://qiita.com/yyoshida15/items/4ea6a0752ff2ba942dff