grasys blog

SSVC で脆弱性のトリアージを自動化する

はじめに

CVSS での脆弱性管理では自組織での状況を反映させた優先度付けができないため、検出される脆弱性が多すぎると何から手をつけて良いかわからなくなり本当に対応しなければいけない脆弱性に迅速に対応できなくなったりします。

SSVC を使うことで優先度付けを行い、どれから優先して対応すればいいかを迅速に判断するためのサポートができます。

公開されている Python モジュールを使って、自動的に優先度付けをしてみようと思います。

SSVC とは

SSVC(Stakeholder-Specific Vulnerability Categorization)とは、脆弱性管理のための対応優先度を判断するフレームワークです。

Decision Tree という決定木を使って分岐点ごとに値を選択することで脆弱性に対する対応方法を算出します。

Github にて対応方法を算出するソースコードが公開 されていますのでこちらを使って対応方法を算出してみたいと思います。

ステークホルダー(Stakeholders)

以下の 3 つがあります。

StakeholdersDescription
サプライヤーソフトウェアを提供する組織
デプロイヤーサプライヤーから提供されたパッチなどを評価し、適用する組織
コーディネーター脆弱性を取りまとめる組織

分岐点(Decision Points)

Decision PointsDescription
Exploitation攻撃実績があるのか、PoC か公開されているかなどで判断します
System Exposureシステムが公開されているかなどで判断します
Utility自動化が可能か、攻撃の有効性などで判断します
Impact影響の大きさなどで判断します

https://certcc.github.io/SSVC/howto/deployer_tree/#deployer-decision-points

Utility is a combination of Automatable and Value Density

とありますが モデルソースコード では「Automatable」のみになっていました。

本記事ではひとまずモデルとソースコードに合わせて「Automatable」のみで判別してみます。

優先度(Decision Points)

算出される優先度は以下の通りになります。

Deployer PriorityDescription
Defer現時点では対応しない
Scheduled定期メンテナンスで対応する
Out-of-cycle次回のサイクルを待たず、迅速に緩和策/修正策を適用する
Immediate全てのリソースを使って、必要であれば通常業務を止めてでも対応する

作ってみる

Docker を使って動作環境を構築します。

  • Dockerfile
FROM python:3.14

RUN apt-get update

RUN mkdir /app

WORKDIR /app

COPY requirements.txt /app/

RUN pip install -r requirements.txt
  • requirements.txt
certcc-ssvc
pydantic

構築します。

$ tree
.
├── app
├── Dockerfile
└── requirements.txt

$ docker build . -t ssvc:latest
$ docker run -v ./app:/app -it --rm ssvc:latest bash

動作確認

まずはデプロイヤーツリーを表示してみます。

  • app/tree.py
from ssvc.decision_tables.ssvc.deployer_dt import LATEST as DeployerDT
from ssvc.decision_tables.helpers import ascii_tree

def main():
    print(ascii_tree(DeployerDT))

if __name__ == "__main__":
    main()
$ python tree.py
Exploitation.. | System Expos.. | Automatable .. | Human Impact.. | Defer, Sched.. |
-------------------------------------------------------------------------------------
├── none
│               ├── small
│               │               ├── no
│               │               │               ├── low
│               │               │               │               └── [defer]
│               │               │               ├── medium
│               │               │               │               └── [defer]
│               │               │               ├── high
│               │               │               │               └── [scheduled]
│               │               │               └── very high
│               │               │                               └── [scheduled]
│               │               └── yes
│               │                               ├── low
│               │                               │               └── [defer]
│               │                               ├── medium
│               │                               │               └── [scheduled]
│               │                               ├── high
│               │                               │               └── [scheduled]
│               │                               └── very high
│               │                                               └── [scheduled]
│               ├── controlled
│               │               ├── no
│               │               │               ├── low
│               │               │               │               └── [defer]
│               │               │               ├── medium
│               │               │               │               └── [scheduled]
│               │               │               ├── high
│               │               │               │               └── [scheduled]
│               │               │               └── very high
│               │               │                               └── [scheduled]
│               │               └── yes
│               │                               ├── low
│               │                               │               └── [scheduled]
│               │                               ├── medium
│               │                               │               └── [scheduled]
│               │                               ├── high
│               │                               │               └── [scheduled]
│               │                               └── very high
│               │                                               └── [scheduled]
│               └── open
│                               ├── no
│                               │               ├── low
│                               │               │               └── [defer]
│                               │               ├── medium
│                               │               │               └── [scheduled]
│                               │               ├── high
│                               │               │               └── [scheduled]
│                               │               └── very high
│                               │                               └── [scheduled]
│                               └── yes
│                                               ├── low
│                                               │               └── [scheduled]
│                                               ├── medium
│                                               │               └── [scheduled]
│                                               ├── high
│                                               │               └── [scheduled]
│                                               └── very high
│                                                               └── [out-of-cycle]
├── public poc
│               ├── small
│               │               ├── no
│               │               │               ├── low
│               │               │               │               └── [defer]
│               │               │               ├── medium
│               │               │               │               └── [scheduled]
│               │               │               ├── high
│               │               │               │               └── [scheduled]
│               │               │               └── very high
│               │               │                               └── [scheduled]
│               │               └── yes
│               │                               ├── low
│               │                               │               └── [scheduled]
│               │                               ├── medium
│               │                               │               └── [scheduled]
│               │                               ├── high
│               │                               │               └── [scheduled]
│               │                               └── very high
│               │                                               └── [scheduled]
│               ├── controlled
│               │               ├── no
│               │               │               ├── low
│               │               │               │               └── [defer]
│               │               │               ├── medium
│               │               │               │               └── [scheduled]
│               │               │               ├── high
│               │               │               │               └── [scheduled]
│               │               │               └── very high
│               │               │                               └── [scheduled]
│               │               └── yes
│               │                               ├── low
│               │                               │               └── [scheduled]
│               │                               ├── medium
│               │                               │               └── [scheduled]
│               │                               ├── high
│               │                               │               └── [scheduled]
│               │                               └── very high
│               │                                               └── [out-of-cycle]
│               └── open
│                               ├── no
│                               │               ├── low
│                               │               │               └── [scheduled]
│                               │               ├── medium
│                               │               │               └── [scheduled]
│                               │               ├── high
│                               │               │               └── [scheduled]
│                               │               └── very high
│                               │                               └── [out-of-cycle]
│                               └── yes
│                                               ├── low
│                                               │               └── [scheduled]
│                                               ├── medium
│                                               │               └── [scheduled]
│                                               ├── high
│                                               │               └── [out-of-cycle]
│                                               └── very high
│                                                               └── [out-of-cycle]
└── active
                ├── small
                │               ├── no
                │               │               ├── low
                │               │               │               └── [scheduled]
                │               │               ├── medium
                │               │               │               └── [scheduled]
                │               │               ├── high
                │               │               │               └── [out-of-cycle]
                │               │               └── very high
                │               │                               └── [out-of-cycle]
                │               └── yes
                │                               ├── low
                │                               │               └── [scheduled]
                │                               ├── medium
                │                               │               └── [out-of-cycle]
                │                               ├── high
                │                               │               └── [out-of-cycle]
                │                               └── very high
                │                                               └── [out-of-cycle]
                ├── controlled
                │               ├── no
                │               │               ├── low
                │               │               │               └── [scheduled]
                │               │               ├── medium
                │               │               │               └── [scheduled]
                │               │               ├── high
                │               │               │               └── [out-of-cycle]
                │               │               └── very high
                │               │                               └── [out-of-cycle]
                │               └── yes
                │                               ├── low
                │                               │               └── [out-of-cycle]
                │                               ├── medium
                │                               │               └── [out-of-cycle]
                │                               ├── high
                │                               │               └── [out-of-cycle]
                │                               └── very high
                │                                               └── [out-of-cycle]
                └── open
                                ├── no
                                │               ├── low
                                │               │               └── [scheduled]
                                │               ├── medium
                                │               │               └── [out-of-cycle]
                                │               ├── high
                                │               │               └── [out-of-cycle]
                                │               └── very high
                                │                               └── [immediate]
                                └── yes
                                                ├── low
                                                │               └── [out-of-cycle]
                                                ├── medium
                                                │               └── [out-of-cycle]
                                                ├── high
                                                │               └── [immediate]
                                                └── very high
                                                                └── [immediate]

やってみた

上記のデプロイヤーツリーの Decision Point を判定して対応優先度を出力してみます。
まずは判定ロジックはなしで、固定の値を返します。
CVE 番号を渡すと下記の通り判定し、対応方針: Defer を出力してみます。

Decision PointValue
ExploitationNone
System ExposureSmall
AutomatableNo
Human ImpactLow
  • main.py
import ssvc
from ssvc.decision_tables.ssvc.deployer_dt import (
    LATEST as DeployerDT,
    Exploitation,
    Exposure,
    Automatable,
    HumanImpact,
)
from ssvc.outcomes.ssvc.dsoi import LATEST as Dsoi
from ssvc.decision_points.base import DecisionPointValue


def exploitation_dp(cveId) -> DecisionPointValue:
    """Exploitation の Decision Point を返す
    none,public poc,active のいずれか
    """
    return next((v for v in Exploitation.values if v.name == "None"))


def system_exposure_dp(cveId) -> DecisionPointValue:
    """System Exposure の Decision Point を返す
    small,controlled,open のいずれか
    """
    return next((v for v in Exposure.values if v.name == "Small"))


def automatable_dp(cveId) -> DecisionPointValue:
    """Automatable の Decision Point を返す
    no,yes のいずれか
    """
    return next((v for v in Automatable.values if v.name == "No"))


def human_impact_dp(cveId) -> DecisionPointValue:
    """Human Impact の Decision Point を返す
    low,medium,high,very high のいずれか
    """
    return next((v for v in HumanImpact.values if v.name == "Low"))


namespace = "ssvc"


def show_dsoi(cve) -> None:
    check_dp = {
        "E": exploitation_dp(cve.get("id")),
        "EXP": system_exposure_dp(cve.get("id")),
        "A": automatable_dp(cve.get("id")),
        "HI": human_impact_dp(cve.get("id")),
    }

    query = {}
    for dp in DeployerDT.decision_points.values():
        if dp.namespace == namespace and not dp.key == "DSOI":
            query[dp.id] = check_dp[dp.key].key

    hit = next(
        (m for m in DeployerDT.mapping if all(m.get(k) == v for k, v in query.items())),
        None,
    )

    matched = next((v for v in Dsoi.values if v.key == hit[DeployerDT.outcome]), None)

    print(f"{cve.get("id"):<20}{matched.name:<15}", end="")
    print(query)


def main() -> None:
    cve = [
        {"id": "CVE-0000-00000"},
    ]

    print(f"{"CVE":<20}{"DSOI":<15}{"QUERY":<5}")
    for c in cve:
        show_dsoi(c)


if __name__ == "__main__":
    main()
$ python main.py
CVE                 DSOI           QUERY
CVE-0000-00000      Defer          {'ssvc:E:1.1.0': 'N', 'ssvc:EXP:1.0.1': 'S', 'ssvc:A:2.0.0': 'N', 'ssvc:HI:2.0.2': 'L'}

自動判定してみる

次は試しに Exploitation を自動で判定してみます。
判定ロジックは CISA KEV に掲載があるかとしました。(None は今回は返しません)

  • ある → Active
  • ない → Public Poc

コードを修正します。
json に対象の CVE番号の記載があるかだけを確認しています。

$ curl https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json > "app/cisa_kev/$(date "+%F").json"
$ diff -u prev.py main.py
--- prev.py  2026-01-13 18:17:14
+++ main.py  2026-01-13 18:17:01
@@ -8,15 +8,23 @@
 )
 from ssvc.outcomes.ssvc.dsoi import LATEST as Dsoi
 from ssvc.decision_points.base import DecisionPointValue
+import json


 def exploitation_dp(cveId) -> DecisionPointValue:
     """Exploitation の Decision Point を返す
     none,public poc,active のいずれか
     """
-    return next((v for v in Exploitation.values if v.name == "None"))
+    file = open("cisa_kev/2026-01-13.json", "r")
+    j = json.load(file)
+    cve_list = [item.get("cveID") for item in j["vulnerabilities"]]

+    if cveId in cve_list:
+        return next((v for v in Exploitation.values if v.name == "Active"))

+    return next((v for v in Exploitation.values if v.name == "Public PoC"))
+
+
 def system_exposure_dp(cveId) -> DecisionPointValue:
     """System Exposure の Decision Point を返す
     small,controlled,open のいずれか
@@ -68,6 +76,7 @@
 def main() -> None:
     cve = [
         {"id": "CVE-0000-00000"},
+        {"id": "CVE-2025-8110"},
     ]

     print(f"{"CVE":<20}{"DSOI":<15}{"QUERY":<5}")

実行してみます。

$ python main.py
CVE                 DSOI           QUERY
CVE-0000-00000      Defer          {'ssvc:E:1.1.0': 'P', 'ssvc:EXP:1.0.1': 'S', 'ssvc:A:2.0.0': 'N', 'ssvc:HI:2.0.2': 'L'}
CVE-2025-8110       Scheduled      {'ssvc:E:1.1.0': 'A', 'ssvc:EXP:1.0.1': 'S', 'ssvc:A:2.0.0': 'N', 'ssvc:HI:2.0.2': 'L'}

KEV に載っている CVE 番号は Exploitation が Active 判定されました。

土台はできたのであとは判定ロジックを構築すれば自動で判別が可能になります。

まとめ

SSVC の自動判定を作成してみました。

判定ロジックは組織ごとに明確に、具体的に定義する必要があります。
一例として以下の様に考えることができるかと思います。
exploitation や automatable は外部公開されている情報で自動判定できそうですね。

SSVC でのトリアージは意思決定の参考にするためのものであり、最終的には現場の判断などが不可欠です。

こちらの公開ソースコードを利用すればパイプラインに組み込んでトリアージ結果を自動通知、といったこともできそうですね。


採用情報
お問い合わせ