本文へ移動します

未来に繋ぐセキュリティ情報発信

Microsoft Defender for Endpoint で収集した脆弱性情報をソフトウェアごとに整理して見えてきたこと

安藤 翔一

こんにちは。Microsoft 製品を使用したサービス開発を行っている安藤です。

以前の記事で Microsoft Defender for Endpoint で収集した脆弱性情報を KQL で出力する方法についてご紹介しました。 Microsoft Defender for Endpoint では脆弱性情報が CVE ID 単位で出力されるため、クライアント端末、サーバで古いソフトウェアを使用している場合は、1台につき1,000件以上の脆弱性が出力される場合もあり、それらを確認して脆弱性の対応を進めるのは現実的ではありません。 本記事では CVE ID 単位で出力されている脆弱性情報をソフトウェア単位でまとめて出力させる方法と、ソフトウェア単位で整理した際に改めて確認した SBOM の確認方法についてもご紹介します。

脆弱性情報をソフトウェア単位で出力する

脆弱性情報は Advanced Hunting 機能を使用して出力します。詳細は以前の記事をご参照ください。

DeviceTvmSoftwareVulnerabilities では以下のフィールドの値が出力されます。

左右にスクロールしてご覧ください。

DeviceId デバイスの一意となる識別子
DeviceNam デバイス名
OSPlatform オペレーティングシステム種別
OSVersio オペレーティングシステムバージョン
OSArchitecture オペレーティングシステムアーキテクチャ
SoftwareVendor ソフトウェアベンダー
SoftwareName ソフトウェア名
SoftwareVersion ソフトウェアバージョン
CveId CVE ID
VulnerabilitySeverityLevel 重大度レベル
RecommendedSecurityUpdate 脆弱性対応に推奨されるパッチ、更新プログラムの情報
RecommendedSecurityUpdateId 脆弱性対応に推奨されるパッチ、更新プログラムの識別子
CveTags CVE に関連するタグ情報

以前の記事の通り、以下クエリのように VulnerabilitySeverityLevel カラム別に件数を集計することができます。しかし、CVE ID 別に集計されるため、組織レベルによっては非常に多くの脆弱性情報が出力されます。

let rank = pack_array("Low","Medium","High","Critical");
DeviceTvmSoftwareVulnerabilities
| summarize count() by VulnerabilitySeverityLevel
| extend rankRowNum = array_index_of(rank, VulnerabilitySeverityLevel)
| sort by rankRowNum desc | project-away rankRowNum

組織内のデバイスを管理するにあたって、個々のデバイスのそれぞれに対して脆弱性の危険度を判断し、脆弱性に対応することは非常に労力がかかります。
組織のセキュリティを管理する立場からすると、危険な脆弱性を含むソフトウェアを特定したうえで、当該ソフトウェアのバージョンアップを組織内に促すようにしたいです。そこで出力される脆弱性情報をソフトウェアとそのバージョンで整理し、件数を少なくすることで意思決定を早める一助とします。

以下のクエリではソフトウェアバージョンごとに脆弱性情報を集計し、当該ソフトウェアに含まれる脆弱性の CVSS スコア最大値、CVSS メトリクスの最も影響の大きいもの、Severity の最大値を出力し、当該ソフトウェアがインストールされているアセットの台数も出力しています。ソフトウェアごとに出力した危険度を確認し、組織に多数インストールされているソフトウェアの脆弱性に関しては注意喚起、少数しかインストールされていないソフトウェアには個別で対応依頼を促すなどの方針を決めることができます。

let rank = pack_array("Low","Medium","High","Critical");
  DeviceTvmSoftwareVulnerabilities
  | join kind=leftouter DeviceTvmSoftwareVulnerabilitiesKB on CveId
  | extend VulnerabilitySeverityLevelInt = array_index_of(rank, VulnerabilitySeverityLevel)
  | summarize MaxCvssScore = max(CvssScore), MaxSeverityInt = max(VulnerabilitySeverityLevelInt), make_set(CvssVector) by SoftwareVendor, SoftwareName, SoftwareVersion, DeviceId
  | summarize arg_max(MaxSeverityInt, *), DeviceCount = count() by SoftwareVendor, SoftwareName, SoftwareVersion
  | extend MaxSeverityLevel = tostring(rank[MaxSeverityInt])
  | extend Cvss_AV = case(
      tostring(set_CvssVector) contains "AV:N", "N", 
      tostring(set_CvssVector) contains "AV:A", "A", 
      tostring(set_CvssVector) contains "AV:L", "L", 
      tostring(set_CvssVector) contains "AV:P", "P",
      "")
  | extend Cvss_AC = case(
      tostring(set_CvssVector) contains "AC:L", "L", 
      tostring(set_CvssVector) contains "AC:M", "M", 
      tostring(set_CvssVector) contains "AC:H", "H",
      "")
  | extend Cvss_Au = case(
      tostring(set_CvssVector) contains "Au:N", "N", 
      tostring(set_CvssVector) contains "Au:S", "S", 
      tostring(set_CvssVector) contains "Au:M", "M",
      "")
  | extend Cvss_PR = case(
      tostring(set_CvssVector) contains "PR:N", "N", 
      tostring(set_CvssVector) contains "PR:L", "L", 
      tostring(set_CvssVector) contains "PR:H", "H",
      "")
  | extend Cvss_UI = case(
      tostring(set_CvssVector) contains "UI:N", "N", 
      tostring(set_CvssVector) contains "UI:R", "R",
      "")
  | extend Cvss_S = case(
      tostring(set_CvssVector) contains "S:U", "U", 
      tostring(set_CvssVector) contains "S:C", "C",
      "")
  | extend Cvss_C = case(
      tostring(set_CvssVector) contains "C:H", "H", 
      tostring(set_CvssVector) contains "C:C", "C", 
      tostring(set_CvssVector) contains "C:L", "L", 
      tostring(set_CvssVector) contains "C:P", "P", 
      tostring(set_CvssVector) contains "C:N", "N",
      "")
  | extend Cvss_I = case(
      tostring(set_CvssVector) contains "I:H", "H", 
      tostring(set_CvssVector) contains "I:C", "C", 
      tostring(set_CvssVector) contains "I:L", "L", 
      tostring(set_CvssVector) contains "I:P", "P", 
      tostring(set_CvssVector) contains "I:N", "N",
      "")
  | extend Cvss_A = case(
      tostring(set_CvssVector) contains "A:H", "H", 
      tostring(set_CvssVector) contains "A:C", "C", 
      tostring(set_CvssVector) contains "A:L", "L", 
      tostring(set_CvssVector) contains "A:P", "P", 
      tostring(set_CvssVector) contains "A:N", "N",
      "")
  | extend Cvss_E = case(
      tostring(set_CvssVector) contains "E:H", "H", 
      tostring(set_CvssVector) contains "E:F", "F", 
      tostring(set_CvssVector) contains "E:P", "P", 
      tostring(set_CvssVector) contains "E:POC", "POC", 
      tostring(set_CvssVector) contains "E:U", "U",
      tostring(set_CvssVector) contains "E:X", "X",
      tostring(set_CvssVector) contains "E:ND", "ND",
      "")
  | extend Cvss_RL = case(
      tostring(set_CvssVector) contains "RL:U", "U", 
      tostring(set_CvssVector) contains "RL:W", "W", 
      tostring(set_CvssVector) contains "RL:WF", "WF", 
      tostring(set_CvssVector) contains "RL:T", "T", 
      tostring(set_CvssVector) contains "RL:TF", "TF",
      tostring(set_CvssVector) contains "RL:O", "O",
      tostring(set_CvssVector) contains "RL:OF", "OF",
      tostring(set_CvssVector) contains "RL:X", "X",
      tostring(set_CvssVector) contains "RL:ND", "ND",
      "")
  | extend Cvss_RC = case(
      tostring(set_CvssVector) contains "RC:C", "C", 
      tostring(set_CvssVector) contains "RC:R", "R", 
      tostring(set_CvssVector) contains "RC:UR", "UR", 
      tostring(set_CvssVector) contains "RC:U", "U", 
      tostring(set_CvssVector) contains "RC:UC", "UC",
      tostring(set_CvssVector) contains "RC:X", "X",
      tostring(set_CvssVector) contains "RC:ND", "ND",
      "")
  | project-away set_CvssVector, MaxSeverityInt

また、ソフトウェアごとに集計した情報も以下のように集計可能です。

let rank = pack_array("Low","Medium","High","Critical");
  DeviceTvmSoftwareVulnerabilities
  | join kind=leftouter DeviceTvmSoftwareVulnerabilitiesKB on CveId
  | extend VulnerabilitySeverityLevelInt = array_index_of(rank, VulnerabilitySeverityLevel)
  | summarize MaxCvssScore = max(CvssScore), MaxSeverityInt = max(VulnerabilitySeverityLevelInt), make_set(CvssVector) by SoftwareVendor, SoftwareName, SoftwareVersion, DeviceId
  | summarize arg_max(MaxSeverityInt, *), DeviceCount = count() by SoftwareVendor, SoftwareName, SoftwareVersion
  | extend MaxSeverityLevel = tostring(rank[MaxSeverityInt])
  | extend Cvss_AV = case(
      tostring(set_CvssVector) contains "AV:N", "N", 
      tostring(set_CvssVector) contains "AV:A", "A", 
      tostring(set_CvssVector) contains "AV:L", "L", 
      tostring(set_CvssVector) contains "AV:P", "P",
      "")
  | extend Cvss_AC = case(
      tostring(set_CvssVector) contains "AC:L", "L", 
      tostring(set_CvssVector) contains "AC:M", "M", 
      tostring(set_CvssVector) contains "AC:H", "H",
      "")
  | extend Cvss_Au = case(
      tostring(set_CvssVector) contains "Au:N", "N", 
      tostring(set_CvssVector) contains "Au:S", "S", 
      tostring(set_CvssVector) contains "Au:M", "M",
      "")
  | extend Cvss_PR = case(
      tostring(set_CvssVector) contains "PR:N", "N", 
      tostring(set_CvssVector) contains "PR:L", "L", 
      tostring(set_CvssVector) contains "PR:H", "H",
      "")
  | extend Cvss_UI = case(
      tostring(set_CvssVector) contains "UI:N", "N", 
      tostring(set_CvssVector) contains "UI:R", "R",
      "")
  | extend Cvss_S = case(
      tostring(set_CvssVector) contains "S:U", "U", 
      tostring(set_CvssVector) contains "S:C", "C",
      "")
  | extend Cvss_C = case(
      tostring(set_CvssVector) contains "C:H", "H", 
      tostring(set_CvssVector) contains "C:C", "C", 
      tostring(set_CvssVector) contains "C:L", "L", 
      tostring(set_CvssVector) contains "C:P", "P", 
      tostring(set_CvssVector) contains "C:N", "N",
      "")
  | extend Cvss_I = case(
      tostring(set_CvssVector) contains "I:H", "H", 
      tostring(set_CvssVector) contains "I:C", "C", 
      tostring(set_CvssVector) contains "I:L", "L", 
      tostring(set_CvssVector) contains "I:P", "P", 
      tostring(set_CvssVector) contains "I:N", "N",
      "")
  | extend Cvss_A = case(
      tostring(set_CvssVector) contains "A:H", "H", 
      tostring(set_CvssVector) contains "A:C", "C", 
      tostring(set_CvssVector) contains "A:L", "L", 
      tostring(set_CvssVector) contains "A:P", "P", 
      tostring(set_CvssVector) contains "A:N", "N",
      "")
  | extend Cvss_E = case(
      tostring(set_CvssVector) contains "E:H", "H", 
      tostring(set_CvssVector) contains "E:F", "F", 
      tostring(set_CvssVector) contains "E:P", "P", 
      tostring(set_CvssVector) contains "E:POC", "POC", 
      tostring(set_CvssVector) contains "E:U", "U",
      tostring(set_CvssVector) contains "E:X", "X",
      tostring(set_CvssVector) contains "E:ND", "ND",
      "")
  | extend Cvss_RL = case(
      tostring(set_CvssVector) contains "RL:U", "U", 
      tostring(set_CvssVector) contains "RL:W", "W", 
      tostring(set_CvssVector) contains "RL:WF", "WF", 
      tostring(set_CvssVector) contains "RL:T", "T", 
      tostring(set_CvssVector) contains "RL:TF", "TF",
      tostring(set_CvssVector) contains "RL:O", "O",
      tostring(set_CvssVector) contains "RL:OF", "OF",
      tostring(set_CvssVector) contains "RL:X", "X",
      tostring(set_CvssVector) contains "RL:ND", "ND",
      "")
  | extend Cvss_RC = case(
      tostring(set_CvssVector) contains "RC:C", "C", 
      tostring(set_CvssVector) contains "RC:R", "R", 
      tostring(set_CvssVector) contains "RC:UR", "UR", 
      tostring(set_CvssVector) contains "RC:U", "U", 
      tostring(set_CvssVector) contains "RC:UC", "UC",
      tostring(set_CvssVector) contains "RC:X", "X",
      tostring(set_CvssVector) contains "RC:ND", "ND",
      "")
  | project-away set_CvssVector, MaxSeverityInt, DeviceId
  | summarize sum(DeviceCount) by MaxSeverityLevel
  | extend rankRowNum = array_index_of(rank, MaxSeverityLevel)
  | sort by rankRowNum desc | project-away rankRowNum

SBOM の確認方法

ソフトウェアごとに整理すると以下のように openssl といった OSS が出力されることがあります。

これらをバージョンアップまたはアンインストールしようとした場合を考えます。これらのソフトウェアは直接インストールされているわけではなく、異なるソフトウェアに同梱されているいわゆる「SBOM」に該当するものであるため、どのソフトウェアに同梱されているかがわからなければバージョンアップやアンインストールを行うことができません。

そこで、これらのソフトウェアがインストールされている場所を確認するために DeviceTvmSoftwareEvidenceBeta テーブルを leftouter join します。
※このテーブルはその名が示す通りベータ版ではあるため、今後テーブル名、カラム名の変更や出力内容が変わる可能性があります。必要に応じて変更したうえでご利用ください。

DeviceTvmSoftwareEvidenceBeta テーブルには DiskPaths というフィールドがあり、こちらにインストール先の情報が記載されていることがあります。
※必ずしも記載されている訳ではないようです。仕様が確認できた場合は追記します。
インストール先の情報からはどのソフトウェアに同梱されているかわかる情報も記載されているため、これらを確認することでアップデートまたはアンインストールの検討を行うことができます。
その他、RegistryPaths といったカラムにも情報が記載されているケースがあります。こちらもソフトウェアの特定に利する内容です。詳細は以下をご確認ください。

※参考:マイクロソフト社「Microsoft Learn Challenge:DeviceTvmSoftwareEvidenceBeta」

まとめ

ここまでお読みいただきありがとうございました。膨大な脆弱性情報をソフトウェアごとに整理する方法や、ソフトウェアのインストール先を調査する方法についてご紹介しました。これらを活用いただくことで脆弱性に対する対応方針の一助になれば幸いです。

また、以前の記事では脆弱性に対して Known Vulnerabilities Catalog の情報を付加して優先度判断材料を増やすクエリについても紹介しています。ぜひご参照ください。 当社では Microsoft Defender for Endpoint のひとつの機能である脆弱性可視化機能を有効活用するための仕組みの検討を進めており、お悩みのお客様と共にベストプラクティスを検討できればと思っておりますので、ご興味がございましたらお気軽にお問い合わせください。

お問い合わせ

製品・サービスに関するお問い合わせはお気軽にご相談ください。

セキュリティソリューションに関する資料請求・お問い合わせ

関連製品・サービス

開催中のセミナー

最新トレンドや業務に役立つ知識が得られる SBテクノロジーのセミナー 参加無料 オンデマンド配信あり

ピックアップ

セミナー情報
クラウドエンジニアブログ
NOZ
メールマガジン登録