3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CVEってなんだ? — ソフトウェアの「診断書」が世界のセキュリティを支える仕組みを完全理解

3
Posted at

この記事の対象読者

  • セキュリティ系のニュースで「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アプリ開発者 — 依存パッケージの脆弱性管理

PythonNode.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 この危機が示す教訓

(;゚д゚)ポカーン ...世界のサイバーセキュリティの根幹が、ひとつの政府契約に依存していたという事実は衝撃的だった。

この事件以降、以下の動きが加速している:

  1. CVE Foundation — CVEプログラムの独立性を確保するための非営利組織
  2. EU独自のCVE採番システム — 米国一国依存からの脱却
  3. 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番号をただの文字列として流し読みするのではなく、その背景にある仕組みを理解しておくこと。それが、自分のコードとシステムを守る第一歩になるはずだ。


参考文献


関連記事

セキュリティ周辺の知識を深めたい方は、以下の記事もあわせてどうぞ。


最後まで読んでいただきありがとうございます。この記事が役に立ったら、いいね・ストックお願いします。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?