你的鎖定檔是否藏著漏洞?
鎖定檔是你程式碼庫裡最誠實的一份文件:它列出建置實際安裝的每一個套件的精確版本,沒有範圍、沒有「最新版」、沒有意外。它同時也悄悄是一份攻擊面清單 — 每一個固定版本,都是一個自你固定它以來、可能已經有人發現漏洞的版本。本文說明掃描器如何把這份清單變成答案:OSV 資料庫如何把版本比對到已知漏洞,為什麼 CISA KEV 標誌比 CVSS 分數更重要,以及為什麼「未發現漏洞」只是關於資料庫的一句話,而不是關於你的程式碼。你可以在SBOM 漏洞掃描器中親自執行這項檢查,它在瀏覽器內解析你的鎖定檔,永不上傳。
鎖定檔固定了什麼 — 以及為什麼它就是攻擊面
現代軟體大多是別人的程式碼。一個典型的 Web 應用會引入數百個套件,而其中每一個又會引入更多:對一個 Web 框架的直接相依,很容易變成兩百個你從未選擇、也從未審查過的間接套件。鎖定檔就是這棵相依樹的記錄 — npm 的 package-lock.json、完全固定的 Python requirements.txt、Go 的 go.mod 和 go.sum — 寫下來,是為了讓每台機器上的每一次安裝都產生相同的相依樹。沒有它,「在我機器上能跑」只是一個願望,而不是事實。
同樣的精確性,也正是鎖定檔成為尋找已知漏洞的正確位置的原因。漏洞資料庫以套件名稱、生態系和精確版本為鍵:lodash 4.17.20 存在漏洞,lodash 4.17.21 沒有,差別只是一次查詢。浮動範圍沒辦法這樣判定 — 掃描器得猜你實際安裝的是哪個版本,而猜測不是一種安全實務。所以鎖定檔衛生守則的第一條樸實無華、不容商量:提交鎖定檔、保持固定,掃描固定下來的相依樹,而不是清單檔案裡的願望清單。
OSV 如何把版本比對到已知漏洞
OSV.dev 是一個公開漏洞資料庫,把來自 GitHub、NVD 和各語言生態的安全公告彙整成一個可查詢的索引。掃描器向它批次傳送名稱、生態系和版本的三元組 — lodash、npm、4.17.20 — 然後取回恰好影響該版本的漏洞編號。隨後完整取得這些編號:摘要、帶修復版本的受影響範圍、作為 CVSS 向量的嚴重程度,以及 CVE 別名。查詢中不會帶上任何關於你專案的資訊 — 只有這些公開套件的公開座標。
這種比對是有意做得「笨」的,而這正是它的長處。它不讀你的程式碼,不判斷你是否呼叫了易受攻擊的函式,也不在乎套件是怎麼被使用的。如果安全公告說 4.17.21 之前的版本受影響,而你固定的是 4.17.20,你就比對上了 — 哪怕你的程式碼從未碰觸過易受攻擊的函式。這種過度回報是一個特性:可及性分析是一個更難、更慢的問題,而那個廉價的問題(「這個版本是否已知有問題?」)已經能抓住你三年前忘了升級的那個相依性。
掃描器看不到什麼
版本比對檢查有一條硬界線,值得直說:它只能找到已經有人回報過的、針對資料庫已知版本的漏洞。昨天才發現、尚未發布的漏洞是隱形的。一個從未有過漏洞、但本身就是惡意的套件 — 熱門名稱的仿冒套件、遭到入侵的發布版本、會向外回傳資料的相依性 — 根本沒有任何 CVE,這類掃描器也不會標記它。你的自有程式碼也是如此:掃描器評判的是你安裝的套件,而不是你寫的程式碼。
正因為這條界線,「未發現漏洞」這個結果需要仔細解讀。它的意思是:在這個資料庫裡、在此時此刻、針對這些精確版本,沒有任何記錄。這是一個有用的否定結果 — 一份新鎖定檔的乾淨掃描確實是好消息 — 但它不是一張合格證書。針對掃描器看不到的東西,防禦手段是不同的工具:在新相依性進入相依樹之前先固定並審查它,持續關注新的安全公告而不是按季查看,並對一個沉寂已久的套件突然發布新版本保持警惕。掃描器是煙霧警報器,不是消防隊。
為什麼 KEV 標誌比高 CVSS 分數更重要
CVSS 分數回答的是一個假設:如果有人利用這個漏洞,會有多嚴重?CISA 已知遭利用漏洞目錄回答的是一個事實:是否有人正在野外利用它?這兩個答案經常不一致。一個評分為 9.8、但還沒有人武器化的漏洞,可以等到下一次計畫升級;一個評分為 5.3、但列入 KEV 清單 — 並由 CISA 設定了修復截止日期 — 的漏洞,值得本週就修補,因為利用程式碼已經存在、並且已經在使用中。
正是這種倒置,讓掃描器把 KEV 的列單獨打上標記,而不是把它們折進嚴重程度排序裡。排序告訴你什麼最嚴重;KEV 標記告訴你什麼最緊急。實際的分流很短:先修 KEV 項目,再修已有公開修復版本的重大項目,其餘的排進下一個升級週期。本站的大量 CVE 分流台把同樣的 CVE 放進利用背景中 — KEV 狀態、EPSS 機率和 CVSS 一起看 — 當一個標記不足以向團隊說明這項工作的必要性時。
提交鎖定檔,在 CI 裡掃描它 — 而不是一個季度一次
沒有提交的鎖定檔,就是不存在的鎖定檔。如果它只存在於某一位開發者的機器上,其他每台機器安裝的,都是當天「最新版」解析出來的版本,而你掃描的那棵樹,並不是你交付的那棵樹。提交它是最便宜的供應鏈控制:它讓安裝可以重現,讓掃描器的答案對每個人都成立,並把一次相依性升級變成一份可以審查的差異,而不是悄悄的漂移。
另一半是節奏。漏洞每天都在發布,所以一個季度跑一次的掃描,只是一張幾天就會過期的快照。有用的形態是:每次觸及鎖定檔的提取請求都跑一次掃描,再加上一份對主分支的排程掃描 — 與測試套件同樣的循環,同樣的性質:一份新的安全公告會在發布的當天就讓綠色的建置變紅,那時修復只是一次版本升級,而不是一次事後檢討。本站的瀏覽器掃描器就是這個循環的手動版本:在 Dependabot 升級之後,或在發布之前貼上鎖定檔,確認你即將交付的那棵樹。
修復版本、破壞性變更,以及何時修補、何時固定
掃描中的每一次比對都帶有一個修復版本 — 漏洞消失的第一個發布版本 — 或者一句誠實的「尚未發布」。常見情況是修補版本或小版本升級,升級只是鎖定檔裡的一行更動。但修復版本並不總是可以安全採用的版本:一次大版本跳躍可能改變你的程式碼所仰賴的行為,而昨天才發布的修復版本,只經過了一天的真實世界檢驗。判斷與任何相依性升級相同:讀更新日誌、跑測試,先升級你的程式碼實際匯入的套件。
當沒有修復版本時,選擇就收窄為三條:用不上的相依性就移除,換一個有人維護的替代品,或者緩解易受攻擊的程式碼路徑 — 不再把不受信任的輸入傳給受影響的函式,或者在安全公告描述的呼叫前加一道檢查 — 並關注安全公告的更新。不應該做的是把易受攻擊的版本固定下來然後忘掉它:固定是一個決定,它值得在鎖定檔或問題追蹤器裡留一句註解,說明原因,並定一個重新檢視的日期。做過任何更動後重新掃描;掃描器只需幾秒,而第二次掃描是證明第一次掃描的發現已經消失的唯一證據。