この記事の対象読者
- セキュリティ系のニュースで「CVE-2025-XXXX」という文字列を見かけて気になっている方
- 脆弱性管理をこれから学びたいエンジニア
- Pythonでセキュリティ系のツールを触ってみたい方
この記事で得られること
1. CVEとは何か — その正体と、なぜ世界中のエンジニアが同じ番号を参照するのかが理解できる
2. CVE番号が発行されるまでの流れと、登場する組織の役割がわかる
3. CVSS・CWE・NVDなど周辺概念との関係が整理できる
4. PythonでCVE情報を取得・分析する実践スクリプトが手に入る
5. 2025年の資金危機と、CVEプログラムの「これから」がわかる
この記事で扱わないこと
- 個別CVEの攻撃手法の詳細(攻撃コードの解説等)
- 企業向け脆弱性管理ツール(Tenable, Qualys等)の導入ガイド
- ペネトレーションテストの実施方法
1. はじめに — なぜCVEを知るべきなのか
ソフトウェアの世界には、毎日のように新しい「病気」が見つかっている。
2025年だけで48,174件もの脆弱性が公開された。1日あたり約131件。もはや人間が手作業で追いかけられる数ではない。
この膨大な脆弱性のひとつひとつに**世界共通の「診断書番号」を振る仕組み。それがCVE(Common Vulnerabilities and Exposures)**だ。
本記事では、CVEを「病院の診療システム」という比喩を使って解説していく。ソフトウェアの脆弱性を「病気」、CVE番号を「診断コード」と捉えると、この仕組みの全体像が驚くほどスッキリ見えてくる。
CVEは「シーブイイー」と読む。"Common Vulnerabilities and Exposures"(共通脆弱性識別子)の略称だ。
読者がこのセクションで得たのは「CVEが存在する理由」だ。次のセクションでは、CVEの構造そのものに踏み込んでいく。
2. CVEの基本構造 — 「診断書番号」のフォーマットを読み解く
2.1 CVE IDのフォーマット
CVE番号は、病院でいえば「診断コード」にあたる。どの病院(ベンダー)に行っても、同じ病気(脆弱性)には同じコードが振られる。だから世界中の医師(セキュリティ研究者)が同じ言語で会話できる。
CVE-2025-32432
│ │ │
│ │ └─ 通し番号(シーケンス番号)
│ └──────── 採番年(≠発見年。採番された年)
└───────────── プレフィックス(固定)
よくある誤解: CVE-2025-XXXXXの「2025」は脆弱性が発見された年ではなく、CVE IDが採番された年だ。採番と公開にタイムラグがあるケースは珍しくない。
2.2 CVEレコードに含まれる情報
ひとつのCVEレコードは、患者の「カルテ」のようなものだ。以下の情報が記載されている。
| フィールド | 内容 | 病院の比喩 |
|---|---|---|
| CVE ID | 一意の識別番号 | 診断コード |
| Description | 脆弱性の概要説明 | 症状の説明 |
| References | 参考情報・アドバイザリへのリンク | 参考文献・検査結果 |
| Affected Products | 影響を受ける製品・バージョン | 罹患した臓器・部位 |
| CNA | 採番した組織 | 診断した専門医 |
2.3 CVEの「状態」
CVEには複数の状態がある。カルテにも「暫定診断」と「確定診断」があるのと同じだ。
| 状態 | 意味 |
|---|---|
| RESERVED | 番号が予約済みだが、まだ詳細は非公開(診察中) |
| PUBLISHED | 脆弱性情報が公開済み(確定診断が出た) |
| REJECTED | 無効と判定された(誤診だった) |
CVE番号の構造を理解したところで、次はこの「診断書」がどのような流れで発行されるのかを見ていこう。
3. CVEが発行されるまでの流れ — 「診断書」ができるプロセス
3.1 登場する組織と役割
CVEの発行プロセスには複数の組織が関わる。病院のシステムに例えると、それぞれの役割がはっきりする。
| 組織 | 役割 | 病院の比喩 |
|---|---|---|
| MITRE | CVEプログラムの運営・管理。1999年に開始 | 病院の運営本部 |
| CNA (CVE Numbering Authority) | CVE番号を採番する権限を持つ組織。2025年時点で365以上 | 各科の専門医(診断コードを発行できる) |
| CVE Board | プログラムの方向性を助言する委員会 | 病院の理事会 |
| NVD (National Vulnerability Database) | NISTが運営する米国の脆弱性データベース。CVSSスコアを付与 | 国立の医療記録データベース |
| CISA | 米国国土安全保障省のサイバーセキュリティ部門。CVEの資金提供元 | 病院への予算を出す行政機関 |
3.2 CVE発行のステップ
大手ベンダー(Microsoft、Google、Apple等)は自らがCNAであり、自社製品の脆弱性に対して直接CVE番号を採番できる。いわば「院内で診断コードを発行できる大病院」だ。
CVEが発行される仕組みを理解したので、次は「重症度をどう測るのか」という話に進もう。CVE単体では「何が壊れているか」しかわからない。「どれくらいヤバいのか」を示すのが、CVSSだ。
4. CVEの周辺概念 — CVSS・CWE・KEV を整理する
CVEは脆弱性の「名前」を付けるだけの仕組みだ。病院の比喩でいえば、CVEは「この患者は○○病です」という診断名。だが医師は診断名だけでなく、重症度、病気の分類、緊急対応の要否も判断する必要がある。
4.1 CVSS — 重症度のトリアージスコア
**CVSS(Common Vulnerability Scoring System)**は、脆弱性の「重症度」を0.0〜10.0のスコアで数値化する仕組みだ。病院でいえば、救急搬送時のトリアージ(緊急度判定)にあたる。
| CVSSスコア | 深刻度 | 病院の比喩 |
|---|---|---|
| 0.0 | なし | 健康 |
| 0.1〜3.9 | Low | 経過観察 |
| 4.0〜6.9 | Medium | 外来治療 |
| 7.0〜8.9 | High | 入院治療 |
| 9.0〜10.0 | Critical | 緊急手術 |
CVSSスコアは複数の評価軸(攻撃元区分、攻撃条件の複雑さ、必要な特権レベル等)を組み合わせて算出される。
\text{CVSS Base Score} = f(\text{Impact}, \text{Exploitability})
CVSSスコアだけで優先度を決めるのは危険。スコアが高くても実際に悪用されていない脆弱性もあれば、スコアが中程度でも攻撃が活発な脆弱性もある。後述するKEVやEPSSと組み合わせて判断すべきだ。
4.2 CWE — 病気の「分類体系」
**CWE(Common Weakness Enumeration)**は、脆弱性の「種類」を分類する体系だ。CVEが個別の「この患者は○○病」なら、CWEは「○○病は感染症の一種である」という医学的分類にあたる。
| CWE ID | 名称 | 概要 |
|---|---|---|
| CWE-79 | Cross-Site Scripting (XSS) | 2025年最多の脆弱性分類。8,000件超 |
| CWE-89 | SQL Injection | データベースへの不正クエリ注入 |
| CWE-862 | Missing Authorization | 認可チェックの欠如 |
| CWE-74 | Injection | 全般的なインジェクション |
| CWE-506 | Embedded Malicious Code | 悪意のあるコードの埋め込み |
4.3 KEV — 「いま流行っている病気」リスト
**KEV(Known Exploited Vulnerabilities)**は、CISAが管理する「実際に攻撃に悪用されていることが確認された脆弱性」のカタログだ。病院でいえば「いま地域で流行している感染症リスト」にあたる。
KEVに載っている脆弱性は今すぐ対応が必要だ。CVSSスコアが中程度でも、KEVに載っていれば優先的にパッチを当てるべきである。
4.4 EPSS — 「今後悪用される確率」の予測
**EPSS(Exploit Prediction Scoring System)**は、ある脆弱性が今後30日以内に悪用される確率を0〜1で予測するモデルだ。病院でいえば「この病気が重症化する確率予測」にあたる。
周辺概念を整理できた。ここまでが「知識編」だ。次のセクションでは、PythonでCVE情報を実際に取得・分析するスクリプトを書いていく。手を動かすと理解が一段深まるはずだ。
5. 実践: PythonでCVE情報を取得・分析する
5.1 環境構成
| 項目 | 環境A(開発) | 環境B(本番/CI) | 環境C(テスト) |
|---|---|---|---|
| OS | Windows 11 | Ubuntu 24.04 (Docker) | WSL2 Ubuntu |
| Python | 3.11 | 3.11 | 3.11 |
| 用途 | ローカル調査 | 定期スキャン | 動作確認 |
5.2 CVE情報取得スクリプト
NVDのAPIを使ってCVE情報を取得し、重症度別に集計するスクリプトだ。
"""
CVE情報取得・分析スクリプト
NVD API 2.0を使用して直近のCVE情報を取得し、重症度別に集計する
"""
import json
import urllib.request
import urllib.parse
from datetime import datetime, timedelta, timezone
from collections import Counter
NVD_API_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0"
def fetch_recent_cves(days: int = 7, max_results: int = 50) -> list[dict]:
"""直近N日間のCVEを取得する"""
end = datetime.now(timezone.utc)
start = end - timedelta(days=days)
params = {
"pubStartDate": start.strftime("%Y-%m-%dT%H:%M:%S.000"),
"pubEndDate": end.strftime("%Y-%m-%dT%H:%M:%S.000"),
"resultsPerPage": str(max_results),
}
url = f"{NVD_API_BASE}?{urllib.parse.urlencode(params)}"
req = urllib.request.Request(url, headers={"User-Agent": "CVE-Analyzer/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode())
return data.get("vulnerabilities", [])
def extract_severity(vuln: dict) -> str:
"""CVSSv3.1のベーススコアから重症度を判定する"""
cve_data = vuln.get("cve", {})
metrics = cve_data.get("metrics", {})
# CVSSv3.1を優先、なければCVSSv3.0を参照
for key in ("cvssMetricV31", "cvssMetricV30"):
if key in metrics:
score = metrics[key][0]["cvssData"]["baseScore"]
if score >= 9.0:
return "CRITICAL"
elif score >= 7.0:
return "HIGH"
elif score >= 4.0:
return "MEDIUM"
elif score > 0:
return "LOW"
return "NONE"
return "UNSCORED"
def analyze_cves(vulns: list[dict]) -> None:
"""CVE情報を重症度別に集計・表示する"""
severity_counter = Counter()
cwe_counter = Counter()
for vuln in vulns:
severity = extract_severity(vuln)
severity_counter[severity] += 1
# CWE情報を抽出
cve_data = vuln.get("cve", {})
for weakness in cve_data.get("weaknesses", []):
for desc in weakness.get("description", []):
cwe_id = desc.get("value", "Unknown")
if cwe_id != "NVD-CWE-noinfo":
cwe_counter[cwe_id] += 1
print("=" * 50)
print(f" CVE分析結果(直近取得分 {len(vulns)} 件)")
print("=" * 50)
# 重症度別集計
print("\n■ 重症度別の件数:")
severity_order = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "NONE", "UNSCORED"]
severity_emoji = {
"CRITICAL": "🔴", "HIGH": "🟠", "MEDIUM": "🟡",
"LOW": "🟢", "NONE": "⚪", "UNSCORED": "❓",
}
for sev in severity_order:
count = severity_counter.get(sev, 0)
bar = "█" * count
emoji = severity_emoji.get(sev, "")
print(f" {emoji} {sev:<10}: {count:>3} {bar}")
# CWEトップ5
print("\n■ CWE(脆弱性分類)トップ5:")
for cwe, count in cwe_counter.most_common(5):
print(f" {cwe:<15}: {count} 件")
def main():
print("NVD APIからCVE情報を取得中...")
try:
vulns = fetch_recent_cves(days=7, max_results=100)
if not vulns:
print("取得できたCVEが0件でした。期間を広げて再試行してください。")
return
analyze_cves(vulns)
except Exception as e:
print(f"エラーが発生しました: {e}")
print("NVD APIのレートリミットに引っかかった可能性があります。")
print("APIキーの取得を検討してください: https://nvd.nist.gov/developers/request-an-api-key")
if __name__ == "__main__":
main()
5.3 特定パッケージの脆弱性チェックスクリプト
自分のPython環境にインストールされているパッケージに既知の脆弱性がないか確認するスクリプトだ。
"""
Python環境の脆弱性簡易チェッカー
pip list の出力からパッケージ名を抽出し、NVD APIで脆弱性を検索する
"""
import json
import subprocess
import urllib.request
import urllib.parse
import time
NVD_API_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0"
def get_installed_packages() -> list[tuple[str, str]]:
"""pip listからインストール済みパッケージを取得"""
result = subprocess.run(
["pip", "list", "--format=json"],
capture_output=True, text=True
)
packages = json.loads(result.stdout)
return [(p["name"], p["version"]) for p in packages]
def search_cves_for_package(package_name: str, max_results: int = 5) -> list[dict]:
"""指定パッケージ名でNVDを検索"""
params = {
"keywordSearch": package_name,
"resultsPerPage": str(max_results),
}
url = f"{NVD_API_BASE}?{urllib.parse.urlencode(params)}"
req = urllib.request.Request(url, headers={"User-Agent": "PkgChecker/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode())
return data.get("vulnerabilities", [])
def check_top_packages(top_n: int = 10) -> None:
"""主要パッケージのCVEを確認"""
packages = get_installed_packages()[:top_n]
print(f"上位 {len(packages)} パッケージの脆弱性を確認中...\n")
for name, version in packages:
try:
vulns = search_cves_for_package(name)
count = len(vulns)
status = "⚠️ 要確認" if count > 0 else "✅ 検出なし"
print(f" {name} ({version}): {status} ({count}件)")
time.sleep(6) # APIレートリミット対策
except Exception as e:
print(f" {name} ({version}): ❌ 取得失敗 ({e})")
if __name__ == "__main__":
check_top_packages()
NVD APIにはレートリミットがある(APIキーなし: 5リクエスト/30秒、APIキーあり: 50リクエスト/30秒)。大量のパッケージをチェックする場合はtime.sleep()を適切に挟むこと。
5.4 環境別の設定ファイル
# config_dev.yaml(開発環境)
nvd_api:
base_url: "https://services.nvd.nist.gov/rest/json/cves/2.0"
api_key: "" # 開発時はキーなしでOK
rate_limit_sec: 6
timeout_sec: 30
scan:
days_range: 7
max_results: 50
severity_filter: ["CRITICAL", "HIGH"]
# config_prod.yaml(本番/CI環境)
nvd_api:
base_url: "https://services.nvd.nist.gov/rest/json/cves/2.0"
api_key: "${NVD_API_KEY}" # 環境変数から取得
rate_limit_sec: 1
timeout_sec: 60
scan:
days_range: 1 # 毎日実行なので直近1日
max_results: 200
severity_filter: ["CRITICAL", "HIGH", "MEDIUM"]
notification:
slack_webhook: "${SLACK_WEBHOOK_URL}"
# config_test.yaml(テスト環境)
nvd_api:
base_url: "https://services.nvd.nist.gov/rest/json/cves/2.0"
api_key: ""
rate_limit_sec: 6
timeout_sec: 10
scan:
days_range: 30
max_results: 10
severity_filter: ["CRITICAL"]
Pythonで実際にCVE情報を触れるようになった。次のセクションでは、CVEにまつわるトラブルとその対処法を整理する。
6. よくあるトラブルと対処法
CVEを扱う現場で遭遇しがちな問題をまとめた。病院の比喩でいえば「よくある誤診・見落としパターン集」だ。
| # | トラブル | 原因 | 対処法 |
|---|---|---|---|
| 1 | NVD APIがHTTP 403を返す | APIキーなしでのレートリミット超過 |
time.sleep(6)を挟む or APIキーを取得する |
| 2 | CVE番号があるのにNVDに情報がない | NVDへのデータ連携が遅延している(バックログ問題) | cve.orgで直接確認する。NVDは分析付加に時間がかかる |
| 3 | CVSSスコアが付いていない | NVDの分析が追いついていない(2025年時点でCPEカバー率57.6%) | ベンダーアドバイザリで独自評価する。EPSSも併用 |
| 4 | 同じ脆弱性に複数のCVE IDがある | 異なるCNAが別々に採番した重複 | REJECTEDステータスを確認。正規のIDに寄せる |
| 5 | CVE IDの年号と実際の発見年が違う | CVE IDの年号は「採番年」であり「発見年」ではない | 採番年と公開日を区別して管理する |
| 6 |
keywordSearchで関係ない結果が大量に返る |
キーワード検索はDescription全文に対するマッチ | CPE名で絞り込む(cpeNameパラメータ使用) |
| 7 | REJECTED状態のCVEをスキャナが検出する | ツールがREJECTED状態を考慮していない | CVEのステータスを必ず確認してからトリアージする |
NVDのバックログ問題は深刻だ。2024年春の処理遅延以降、CVE公開からNVDでの分析完了まで数週間〜数ヶ月かかるケースがある。NVDだけに依存せず、ベンダーアドバイザリやGitHub Security Advisory(GHSA)も併用すべきだ。
7. ユースケース別ガイド
ユースケース1: Webアプリ開発者 — 依存パッケージの脆弱性管理
PythonやNode.jsでWebアプリを開発している場合、使用ライブラリの脆弱性を定期的にチェックする必要がある。
# Python: pipのaudit機能(pip 22.2+)
pip audit
# Node.js: npm audit
npm audit
# GitHub Dependabotを有効化(リポジトリの Settings > Security)
# → 脆弱性が見つかるとPRが自動生成される
ポイント: これらのツールは内部的にCVE/GHSAデータベースを参照している。CVEという「共通言語」があるからこそ、ツール横断で同じ脆弱性を追跡できる。
ユースケース2: インフラエンジニア — CIパイプラインでの脆弱性スキャン
Dockerイメージの脆弱性をCIで自動チェックする構成例。
# .github/workflows/vuln-scan.yml
name: Vulnerability Scan
on:
push:
branches: [main]
schedule:
- cron: '0 9 * * 1' # 毎週月曜9時に定期実行
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
exit-code: '1' # CRITICAL/HIGHがあればCIを失敗させる
ユースケース3: セキュリティ担当者 — KEVベースの緊急対応フロー
CISAのKEV(Known Exploited Vulnerabilities)カタログに新規追加されたCVEは、即座に対応すべき最優先事項だ。
"""
KEVカタログの新規追加CVEを通知するスクリプト
"""
import json
import urllib.request
from datetime import datetime, timedelta, timezone
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
def check_new_kev_entries(days: int = 1) -> list[dict]:
"""直近N日以内に追加されたKEVエントリを取得"""
req = urllib.request.Request(KEV_URL, headers={"User-Agent": "KEVChecker/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode())
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
new_entries = []
for vuln in data.get("vulnerabilities", []):
added = datetime.strptime(
vuln["dateAdded"], "%Y-%m-%d"
).replace(tzinfo=timezone.utc)
if added >= cutoff:
new_entries.append(vuln)
return new_entries
if __name__ == "__main__":
entries = check_new_kev_entries(days=7)
if entries:
print(f"⚠️ 直近7日間でKEVに {len(entries)} 件追加されました:\n")
for e in entries:
print(f" {e['cveID']}: {e['vendorProject']} - {e['product']}")
print(f" 対応期限: {e['dueDate']}")
print()
else:
print("✅ 直近7日間のKEV新規追加はありません。")
8. 2025年の資金危機とCVEの未来 — 「病院が閉鎖されかけた日」
8.1 何が起きたのか
2025年4月15日、セキュリティ業界に激震が走った。
CVEプログラムを25年間運営してきたMITRE社への米国政府からの資金が、翌4月16日に失効するという情報が流れたのだ。...orz
これは病院の比喩でいえば「国立の診断コード管理システムが、翌日から停止します」という通告に等しい。全世界のセキュリティツール、脆弱性データベース、インシデント対応フローがCVEに依存しているなかで、その根幹が止まりかけた。
8.2 危機の経緯
| 日付 | 出来事 |
|---|---|
| 2025年4月15日 | MITREのBarsoum副社長がCVE理事会に資金失効を通知。Krebs on Securityが報道 |
| 2025年4月16日 | CISAが緊急で11ヶ月の契約延長を実行。即日でサービス継続が確定 |
| 2025年4月16日 | CVE理事会メンバーがCVE Foundation(非営利団体)の設立を発表 |
| 2026年1月 | CISAがCVEプログラムの予算を「保護された項目」に格上げ。恒久的な資金確保へ |
| 2026年3月 | EUおよび国際連合体が独自のCVE採番システムを立ち上げ |
8.3 この危機が示す教訓
(;゚д゚)ポカーン ...世界のサイバーセキュリティの根幹が、ひとつの政府契約に依存していたという事実は衝撃的だった。
この事件以降、以下の動きが加速している:
- CVE Foundation — CVEプログラムの独立性を確保するための非営利組織
- EU独自のCVE採番システム — 米国一国依存からの脱却
- AIによる脆弱性報告の爆増 — GitHubへの脆弱性報告が90日間で224%増加(AI生成レポートの影響)
2026年3月時点で、CVEプログラムの資金問題は一応の解決を見ている。CISAの予算内で「保護された項目」に格上げされ、毎年の契約更新で綱渡りする状況は改善された。
9. 学習ロードマップ
CVEの世界は奥深い。ここまでの知識を踏まえて、次に何を学ぶべきか。
レベル1: 基礎固め(本記事の内容 + α)
- CVEの基本概念と番号体系
- CVSS/CWE/KEV/EPSSとの関係
- NVD APIの詳細仕様(フィルタ条件、ページネーション)
- GitHub Security Advisory(GHSA)の仕組み
レベル2: 実務適用
- 脆弱性管理ポリシーの策定(SLA、対応期限の設定)
- CI/CDパイプラインへの脆弱性スキャン組み込み
- SBOM(Software Bill of Materials)の導入と運用
- CNA登録プロセスの理解
レベル3: 高度なセキュリティ運用
- CVE Numbering Authority(CNA)としての活動
- 脆弱性の報告と協調的開示(Coordinated Vulnerability Disclosure)
- SSVC(Stakeholder-Specific Vulnerability Categorization)による高度なトリアージ
- VEX(Vulnerability Exploitability eXchange)ドキュメントの作成
まとめ
CVEは、ソフトウェア世界の「診断書番号」だ。
1999年にMITREが始めたこのプログラムは、26年以上にわたって世界中のセキュリティ対応を支えてきた。2025年には48,174件もの脆弱性が登録され、2025年4月には資金失効という存亡の危機すら経験した。
正直なところ、CVEの仕組みを調べれば調べるほど「こんな重要なインフラが、こんなに脆い基盤の上に成り立っていたのか」と驚く。セキュリティの世界では「単一障害点を作るな」とよく言われるが、CVEプログラム自体がまさに単一障害点だったというのは皮肉な話だ。
とはいえ、CVE Foundationの設立やEUの動きを見ると、この仕組みが消えることはないだろう。形を変えながらも「脆弱性に共通の名前を付ける」という思想は生き続ける。
エンジニアとして、CVE番号をただの文字列として流し読みするのではなく、その背景にある仕組みを理解しておくこと。それが、自分のコードとシステムを守る第一歩になるはずだ。
参考文献
関連記事
セキュリティ周辺の知識を深めたい方は、以下の記事もあわせてどうぞ。
- Dockerってなんだ? — コンテナの脆弱性スキャンに必要な前提知識
- Kubernetesってなんだ? — クラスタ環境のセキュリティ管理
- Pythonで始めるプログラミング — スクリプト作成の基礎
- PyPIってなんだ? — サプライチェーン攻撃の理解に
- MCPってなんだ? — AIエージェントのセキュリティ課題
最後まで読んでいただきありがとうございます。この記事が役に立ったら、いいね・ストックお願いします。