包包baobaolin.com
date
entry
052
topic
integration
rev
—

用 dateAdded 篩 CISA KEV 做每週清單,會漏掉舊條目的改動:35 個版本的逐欄 diff

我有一支做每週觀察清單的腳本,從 CISA 的 KEV(已知遭利用漏洞清單)JSON 裡挑出 dateAdded 落在那一週的條目。這樣只看得到「新加入」的漏洞。我把 8 月 31 日到 10 月 16 日的 35 個版本逐欄比對一次,舊條目在這段時間裡被改了好幾次,其中 9 筆被改成「已知用於勒索軟體」;照這支腳本的寫法,這 9 筆不會出現在這段期間任何一週的清單裡。

CISA 的 KEV 清單有一個公開的 JSON feed,每一筆都有 dateAdded(加入日期)。要做每週的漏洞觀察清單,最直接的做法是篩 dateAdded 落在這週的條目。我自己的腳本就是這樣寫的:args.since <= v['dateAdded'] <= args.until。

但 KEV 裡的舊條目會被改,而這次觀察到的修改,沒有一次同時動到 dateAdded。這篇要談的是用加入日期篩會漏掉什麼,依序是:資料從哪來、35 個版本裡實際改了什麼、哪些改動對觀察清單有影響、版本號為什麼不能當鍵,以及我打算改成的比對方式。

資料:kev-data 的 git 歷史

cisa.gov 上的 feed 網址給的是當前內容,檔案本身不附修訂紀錄。CISA 另外在 GitHub 維護 cisagov/kev-data,README 說使用者可以預期它和 cisa.gov 在幾分鐘內同步,也直接講了這個 repo 的用處:KEV 沒有事後容易取得的修訂紀錄,git 的提交歷史可以讓追蹤變更更透明。

我用 GitHub API 列出 known_exploited_vulnerabilities.json 從 2026-09-01 起的 34 個 commit,加上 8 月 31 日的一個做基準,共 35 個版本,各存一份。最新那一版和我同一天從 cisa.gov 抓的 feed 內容完全相同(catalogVersion 2026.10.16,count 1748)。比對方法是以 cveID 當鍵,每兩個相鄰版本比一次:哪些鍵新出現、哪些消失、同一個鍵的哪些欄位值不同。能比的是 GitHub 上留下來的版本;如果 cisa.gov 上有過沒被提交的短暫狀態,這裡看不到。

35 個版本裡改了什麼

新增條目               61
刪除條目                0
欄位改動(不含新欄位)
  knownRansomwareCampaignUse   9   全部 Unknown → Known
  forensicTriage               5
  shortDescription             4
  dueDate                      2
  notes                        1
新欄位 forensicTriage   1,687   9 月 1 日一次加到全部條目

新增的 61 筆,dateAdded 都不早於它出現那天的 commit 日期,這批樣本裡沒有「用較早的加入日期回填」的情況;只要每週都有跑、日期區間接得上,按加入日期篩新條目在這段期間不會因為回填而漏。問題在其餘那幾行:它們改的是早就在清單裡的條目。按加入日期篩,只看得到條目在不在這週的區間裡,看不出它被改過;加入日期已經在區間外的條目,則根本不會再出現。

也有剛好落在區間裡的例外:CVE-2023-22894 是 10 月 8 日加入的,同一天的第二次發布把它的 forensicTriage 從 No 改成 Yes。如果是在第二次發布之後抓的,那一週的清單會因為加入日期落在區間裡選到它;但我的腳本不輸出 forensicTriage,也看不出它當天由 No 改成 Yes。

哪些改動該進觀察清單

最值得注意的是 9 筆勒索軟體標記。knownRansomwareCampaignUse 在 schema 裡的說明是:已知被用在勒索軟體行動就是 Known,CISA 沒有確認就是 Unknown。這 9 筆的 dateAdded 從 2021-11-03 到 2026-08-18,例如 CVE-2016-7255 和 CVE-2019-0859 都是 2021 年 11 月加入的,9 月 9 日(UTC)才改成 Known。它們的加入日期都不在這次觀察期間的任何一週裡,對一個用 dateAdded 篩的週報來說,改成 Known 這件事等於沒發生。

我的讀法是,如果組織還在用受影響的產品、也還沒處理,「舊漏洞被確認用於勒索軟體」比「多一個新漏洞」更該提醒,因為它已經公開很久,攻擊者有時間把它做成工具。這是推論,CISA 的資料沒有說哪一種比較重要。

dueDate 在 schema 裡是 requiredAction(要求的處置)的到期日。兩筆的改動方向相反:CVE-2026-86950 從 2026-10-02 延到 2026-10-13;CVE-2022-0995 從 2026-09-09 提前到 2026-08-29,改動出現在 10 月 8 日(UTC)的版本,新的期限在改的時候已經過了。為什麼這樣改,JSON 裡沒有說明。如果你在 10 月 8 日的修改之前就記下這兩筆的期限,之後又只處理新增、不刷新舊紀錄,記下的就會停在舊值。

forensicTriage 是 9 月 1 日新加的欄位,schema 的說明是依 BOD 26-04 是否需要鑑識分流;schema 只規定它是字串,這批樣本裡的值只有 Yes 和 No。那一天全部 1,687 筆都多了這個欄位,所以那次比對每一筆都算「有改」。之後有 5 筆在 Yes 和 No 之間改了。10 月 16 日的版本裡,Yes 有 87 筆。如果你的程式用固定欄位的模型、拒收未知欄位,又還沒加上這個欄位,下一次讀到 9 月 1 日之後的版本就會解析失敗;如果只讀固定幾個欄位、也沒有比對,就不會發現多了這個欄位。我的腳本用 json.load 讀,屬於後者。

catalogVersion 不能當版本鍵

比對時我原本想用 catalogVersion 標記每個版本,結果發現這批樣本裡它只有日期。9 月 9 日有兩次發布,catalogVersion 都是 2026.09.09,dateReleased 分別是 12:56:45Z 和 19:00:50Z;9 月 9 日的第一次發布改了 3 筆的勒索軟體標記,第二次新增了 4 筆。9 月 16 日有三次發布,也都是 2026.09.16。

所以「這個 catalogVersion 我看過了」不代表內容一樣。如果腳本處理過當天第一次發布,又只用 catalogVersion 判斷要不要重新處理,當天後面的發布就會被跳過。這 35 個版本的 dateReleased 都不同,但 schema 沒有保證它唯一;要分辨內容,我會算整個檔案的雜湊,從 GitHub 補的版本再記下 commit SHA,dateReleased 留著當發布時間。

改成逐欄比對

如果是我重寫這支週報,我會讓它每次把抓到的 JSON 存一份,下次跑時和上一份比,而不是篩日期。核心大概這樣(用一個哨兵值,才分得出「欄位不存在」和「值是 null」):

MISSING = object()
prev = {v["cveID"]: v for v in json.load(open(old))["vulnerabilities"]}
curr = {v["cveID"]: v for v in json.load(open(new))["vulnerabilities"]}
added   = [k for k in curr if k not in prev]
removed = [k for k in prev if k not in curr]
changed = {k: {f: (prev[k].get(f, MISSING), curr[k].get(f, MISSING))
               for f in prev[k].keys() | curr[k].keys()
               if prev[k].get(f, MISSING) != curr[k].get(f, MISSING)}
           for k in curr if k in prev and prev[k] != curr[k]}

新增的照舊列出來;改動的依欄位分:勒索軟體標記和處置期限的改動進清單;forensicTriage 翻轉也列出來,因為它改變的是清單依 BOD 26-04 標示的鑑識分流要求;這次那幾次拼字和語句的修訂只記錄,說明文字如果改到處置內容,再另外看。刪除這段期間是 0,但 schema 和 README 都沒有說條目不會被移除,所以這一類也要留著。第一次跑要先建立基準;中間漏跑了幾週,拿手上最後一份比就能看到淨變化,要還原中間的變化,再從 kev-data 的 git 歷史補那段期間留存的版本,這篇的資料就是這樣來的。

一週跑一次也會漏東西。同一筆在兩次執行之間改了又改回來,比對頭尾兩份看不出來;這 35 個版本裡沒有這種情況,但頻率低的比對本來就看不到中間狀態。

如果只記得一件事

dateAdded 只告訴你條目什麼時候進來,不告訴你它後來被改了什麼;8 月底到 10 月中,9 筆舊漏洞被改成「已知用於勒索軟體」,用加入日期篩的週報看不出來。存下每次抓到的版本、以 cveID 逐欄比對,版本用檔案雜湊區分,不用 catalogVersion。

修訂紀錄

  1. 首次發布